1 /* 2 * Copyright (C) 2022 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 android.mediav2.common.cts; 18 19 import static android.media.MediaCodecInfo.CodecProfileLevel.*; 20 import static android.media.MediaFormat.PICTURE_TYPE_B; 21 import static android.media.MediaFormat.PICTURE_TYPE_I; 22 import static android.media.MediaFormat.PICTURE_TYPE_P; 23 import static android.media.MediaFormat.PICTURE_TYPE_UNKNOWN; 24 25 import android.media.MediaCodec; 26 import android.media.MediaFormat; 27 import android.util.Pair; 28 29 import org.junit.Assert; 30 31 import java.nio.ByteBuffer; 32 import java.util.ArrayList; 33 import java.util.HashMap; 34 35 /** 36 * This class contains utility functions that parse compressed bitstream and returns metadata 37 * necessary for validation. This is by no means a thorough parser that is capable of parsing all 38 * syntax elements of a bitstream. This is designed to handle only the requirements of mediav2 39 * test suite. 40 * <p> 41 * Currently this class hosts utils that can, 42 * <ul> 43 * <li>Return frame type of the access units of avc, hevc, av1.</li> 44 * <li>Return profile/level information of avc, hevc, av1, vp9, mpeg4, h263, aac</li> 45 * </ul> 46 */ 47 public class BitStreamUtils { getHashMapVal(HashMap<Integer, Integer> obj, int key)48 public static int getHashMapVal(HashMap<Integer, Integer> obj, int key) { 49 Integer val = obj.get(key); 50 return val == null ? -1 : val; 51 } 52 53 static class ParsableBitArray { 54 protected final byte[] mData; 55 protected final int mOffset; 56 protected final int mLimit; 57 protected int mCurrByteOffset; 58 protected int mCurrBitOffset; 59 ParsableBitArray(byte[] data, int offset, int limit)60 ParsableBitArray(byte[] data, int offset, int limit) { 61 mData = data; 62 mOffset = offset; 63 mLimit = limit; 64 mCurrByteOffset = offset; 65 mCurrBitOffset = 0; 66 } 67 readBit()68 public boolean readBit() { 69 if (mCurrByteOffset >= mLimit) { 70 throw new ArrayIndexOutOfBoundsException( 71 String.format("Accessing bytes at offset %d, buffer limit %d", 72 mCurrByteOffset, mLimit)); 73 } 74 boolean returnValue = (mData[mCurrByteOffset] & (0x80 >> mCurrBitOffset)) != 0; 75 if (++mCurrBitOffset == 8) { 76 mCurrBitOffset = 0; 77 mCurrByteOffset++; 78 } 79 return returnValue; 80 } 81 readBits(int numBits)82 public int readBits(int numBits) { 83 if (numBits > 32) { 84 throw new IllegalArgumentException("readBits Exception: maximum storage space of " 85 + "return value of readBits : 32, less than bits to read : " + numBits); 86 } 87 int value = 0; 88 for (int i = 0; i < numBits; i++) { 89 value <<= 1; 90 value |= (readBit() ? 1 : 0); 91 } 92 return value; 93 } 94 readBitsLong(int numBits)95 public long readBitsLong(int numBits) { 96 if (numBits > 64) { 97 throw new IllegalArgumentException("readBitsLong Exception: maximum storage space " 98 + "of return value of readBits : 64, less than bits to read : " + numBits); 99 } 100 long value = 0; 101 for (int i = 0; i < numBits; i++) { 102 value <<= 1; 103 value |= (readBit() ? 1 : 0); 104 } 105 return value; 106 } 107 } 108 109 static class NalParsableBitArray extends ParsableBitArray { NalParsableBitArray(byte[] data, int offset, int limit)110 NalParsableBitArray(byte[] data, int offset, int limit) { 111 super(data, offset, limit); 112 } 113 114 @Override readBit()115 public boolean readBit() { 116 // emulation prevention 117 if (mCurrBitOffset == 0 && (mCurrByteOffset - 2 > mOffset) 118 && mData[mCurrByteOffset] == (byte) 0x03 119 && mData[mCurrByteOffset - 1] == (byte) 0x00 120 && mData[mCurrByteOffset - 2] == (byte) 0x00) { 121 mCurrByteOffset++; 122 } 123 return super.readBit(); 124 } 125 readUEV()126 public int readUEV() { 127 int leadingZeros = 0; 128 while (!readBit()) { 129 leadingZeros++; 130 } 131 return (1 << leadingZeros) - 1 + (leadingZeros > 0 ? readBits(leadingZeros) : 0); 132 } 133 } 134 135 static class ObuParsableBitArray extends ParsableBitArray { ObuParsableBitArray(byte[] data, int offset, int limit)136 ObuParsableBitArray(byte[] data, int offset, int limit) { 137 super(data, offset, limit); 138 } 139 uvlc()140 public long uvlc() { 141 int leadingZeros = 0; 142 while (true) { 143 boolean done = readBit(); 144 if (done) { 145 break; 146 } 147 leadingZeros++; 148 } 149 if (leadingZeros >= 32) { 150 return (1L << 32) - 1; 151 } 152 int value = readBits(leadingZeros); 153 return value + (1L << leadingZeros) - 1; 154 } 155 leb128()156 public int[] leb128() { 157 int value = 0, bytesRead = 0; 158 for (int count = 0; count < 8; count++) { 159 int leb128_byte = readBits(8); 160 value |= (leb128_byte & 0x7f) << (count * 7); 161 bytesRead++; 162 if ((leb128_byte & 0x80) == 0) break; 163 } 164 return new int[]{bytesRead, value}; 165 } 166 } 167 168 public abstract static class ParserBase { 169 protected byte[] mData; 170 protected int mOffset; 171 protected int mLimit; 172 set(byte[] data, int offset, int limit)173 protected void set(byte[] data, int offset, int limit) { 174 mData = data; 175 mOffset = offset; 176 mLimit = limit; 177 } 178 getFrameType()179 public abstract int getFrameType(); 180 getProfileLevel(boolean isCsd)181 public abstract Pair<Integer, Integer> getProfileLevel(boolean isCsd); 182 183 // .first = profile, .second = level plToPair(int profile, int level)184 public Pair<Integer, Integer> plToPair(int profile, int level) { 185 return Pair.create(profile, level); 186 } 187 } 188 189 static class Mpeg4Parser extends ParserBase { 190 @Override getFrameType()191 public int getFrameType() { 192 return PICTURE_TYPE_UNKNOWN; 193 } 194 195 @Override getProfileLevel(@uppressWarnings"unused") boolean isCsd)196 public Pair<Integer, Integer> getProfileLevel(@SuppressWarnings("unused") boolean isCsd) { 197 ParsableBitArray bitArray = new ParsableBitArray(mData, mOffset, mLimit); 198 Assert.assertEquals(0, bitArray.readBits(8)); 199 Assert.assertEquals(0, bitArray.readBits(8)); 200 Assert.assertEquals(1, bitArray.readBits(8)); 201 Assert.assertEquals(0xb0, bitArray.readBits(8)); 202 int profileLevel = bitArray.readBits(8); 203 switch (profileLevel) { 204 case 0x08: return plToPair(MPEG4ProfileSimple, MPEG4Level0); 205 case 0x01: return plToPair(MPEG4ProfileSimple, MPEG4Level1); 206 case 0x02: return plToPair(MPEG4ProfileSimple, MPEG4Level2); 207 case 0x03: return plToPair(MPEG4ProfileSimple, MPEG4Level3); 208 case 0xf0: return plToPair(MPEG4ProfileAdvancedSimple, MPEG4Level0); 209 case 0xf1: return plToPair(MPEG4ProfileAdvancedSimple, MPEG4Level1); 210 case 0xf2: return plToPair(MPEG4ProfileAdvancedSimple, MPEG4Level2); 211 case 0xf3: return plToPair(MPEG4ProfileAdvancedSimple, MPEG4Level3); 212 case 0xf7: return plToPair(MPEG4ProfileAdvancedSimple, MPEG4Level3b); 213 case 0xf4: return plToPair(MPEG4ProfileAdvancedSimple, MPEG4Level4); 214 case 0xf5: return plToPair(MPEG4ProfileAdvancedSimple, MPEG4Level5); 215 default: return null; 216 } 217 } 218 } 219 220 static class H263Parser extends ParserBase { 221 @Override getFrameType()222 public int getFrameType() { 223 return PICTURE_TYPE_UNKNOWN; 224 } 225 226 @Override getProfileLevel(@uppressWarnings"unused") boolean isCsd)227 public Pair<Integer, Integer> getProfileLevel(@SuppressWarnings("unused") boolean isCsd) { 228 ParsableBitArray bitArray = new ParsableBitArray(mData, mOffset, mLimit); 229 Assert.assertEquals("bad psc", 0x20, bitArray.readBits(22)); 230 bitArray.readBits(8); // tr 231 Assert.assertEquals(1, bitArray.readBits(1)); 232 Assert.assertEquals(0, bitArray.readBits(1)); 233 bitArray.readBits(1); // split screen 234 bitArray.readBits(1); // camera indicator 235 bitArray.readBits(1); // freeze indicator 236 int sourceFormat = bitArray.readBits(3); 237 int picType; 238 int umv = 0, sac = 0, ap = 0, pb = 0; 239 int aic = 0, df = 0, ss = 0, rps = 0, isd = 0, aiv = 0, mq = 0; 240 int rpr = 0, rru = 0; 241 if (sourceFormat == 7) { 242 int ufep = bitArray.readBits(3); 243 if (ufep == 1) { 244 sourceFormat = bitArray.readBits(3); 245 bitArray.readBits(1); // custom pcf 246 umv = bitArray.readBits(1); 247 sac = bitArray.readBits(1); 248 ap = bitArray.readBits(1); 249 aic = bitArray.readBits(1); 250 df = bitArray.readBits(1); 251 ss = bitArray.readBits(1); 252 rps = bitArray.readBits(1); 253 isd = bitArray.readBits(1); 254 aiv = bitArray.readBits(1); 255 mq = bitArray.readBits(1); 256 Assert.assertEquals(1, bitArray.readBits(1)); 257 Assert.assertEquals(0, bitArray.readBits(3)); 258 } 259 picType = bitArray.readBits(3); 260 rpr = bitArray.readBits(1); 261 rru = bitArray.readBits(1); 262 bitArray.readBits(1); // rtype 263 Assert.assertEquals(0, bitArray.readBits(1)); // reserved 264 Assert.assertEquals(0, bitArray.readBits(1)); // reserved 265 Assert.assertEquals(1, bitArray.readBits(1)); // start code emulation 266 } else { 267 picType = bitArray.readBits(1); 268 umv = bitArray.readBits(1); 269 sac = bitArray.readBits(1); 270 ap = bitArray.readBits(1); 271 pb = bitArray.readBits(1); 272 } 273 int profile = H263ProfileBaseline; 274 if (ap == 1) profile = H263ProfileBackwardCompatible; 275 if (aic == 1 && df == 1 && ss == 1 && mq == 1) profile = H263ProfileISWV2; 276 return plToPair(profile, -1); 277 } 278 } 279 280 static class AvcParser extends ParserBase { 281 private static final int NO_NAL_UNIT_FOUND = -1; 282 private static final HashMap<Integer, Integer> LEVEL_MAP = new HashMap<>() { 283 { 284 put(10, AVCLevel1); 285 put(11, AVCLevel11); 286 put(12, AVCLevel12); 287 put(13, AVCLevel13); 288 put(20, AVCLevel2); 289 put(21, AVCLevel21); 290 put(22, AVCLevel22); 291 put(30, AVCLevel3); 292 put(31, AVCLevel31); 293 put(32, AVCLevel32); 294 put(40, AVCLevel4); 295 put(41, AVCLevel41); 296 put(42, AVCLevel42); 297 put(50, AVCLevel5); 298 put(51, AVCLevel51); 299 put(52, AVCLevel52); 300 put(60, AVCLevel6); 301 put(61, AVCLevel61); 302 put(62, AVCLevel62); 303 } 304 }; 305 getNalUnitStartOffset(byte[] dataArray, int start, int limit)306 private int getNalUnitStartOffset(byte[] dataArray, int start, int limit) { 307 for (int pos = start; pos + 3 < limit; pos++) { 308 if ((pos + 3) < limit && dataArray[pos] == 0 && dataArray[pos + 1] == 0 309 && dataArray[pos + 2] == 1) { 310 return pos + 3; 311 } else if ((pos + 4) < limit && dataArray[pos] == 0 && dataArray[pos + 1] == 0 312 && dataArray[pos + 2] == 0 && dataArray[pos + 3] == 1) { 313 return pos + 4; 314 } 315 } 316 return NO_NAL_UNIT_FOUND; 317 } 318 getNalUnitType(byte[] dataArray, int nalUnitOffset)319 private static int getNalUnitType(byte[] dataArray, int nalUnitOffset) { 320 return dataArray[nalUnitOffset] & 0x1F; 321 } 322 323 @Override getFrameType()324 public int getFrameType() { 325 for (int pos = mOffset; pos < mLimit; ) { 326 int offset = getNalUnitStartOffset(mData, pos, mLimit); 327 if (offset == NO_NAL_UNIT_FOUND) return PICTURE_TYPE_UNKNOWN; 328 int nalUnitType = getNalUnitType(mData, offset); 329 if (nalUnitType == 1 || nalUnitType == 2 || nalUnitType == 5) { // coded slice 330 NalParsableBitArray bitArray = new NalParsableBitArray(mData, offset, mLimit); 331 Assert.assertEquals(0, bitArray.readBits(1)); // forbidden zero bit 332 bitArray.readBits(7); // nal_ref_idc + nal_unit_type 333 bitArray.readUEV(); // first_mb_in_slice 334 int sliceType = bitArray.readUEV(); 335 if (sliceType % 5 == 0) { 336 return PICTURE_TYPE_P; 337 } else if (sliceType % 5 == 1) { 338 return PICTURE_TYPE_B; 339 } else if (sliceType % 5 == 2) { 340 return PICTURE_TYPE_I; 341 } else { 342 return PICTURE_TYPE_UNKNOWN; 343 } 344 } 345 pos = offset; 346 } 347 return PICTURE_TYPE_UNKNOWN; 348 } 349 350 @Override getProfileLevel(@uppressWarnings"unused") boolean isCsd)351 public Pair<Integer, Integer> getProfileLevel(@SuppressWarnings("unused") boolean isCsd) { 352 for (int pos = mOffset; pos < mLimit; ) { 353 int offset = getNalUnitStartOffset(mData, pos, mLimit); 354 if (offset == NO_NAL_UNIT_FOUND) return null; 355 if (getNalUnitType(mData, offset) == 7) { // seq_parameter_set_rbsp 356 NalParsableBitArray bitArray = new NalParsableBitArray(mData, offset, mLimit); 357 Assert.assertEquals(0, bitArray.readBits(1)); // forbidden zero bit 358 bitArray.readBits(7); // nal_ref_idc + nal_unit_type 359 int profileIdc = bitArray.readBits(8); 360 int constraintSet0Flag = bitArray.readBits(1); 361 int constraintSet1Flag = bitArray.readBits(1); 362 int constraintSet2Flag = bitArray.readBits(1); 363 int constraintSet3Flag = bitArray.readBits(1); 364 int constraintSet4Flag = bitArray.readBits(1); 365 int constraintSet5Flag = bitArray.readBits(1); 366 Assert.assertEquals(0, bitArray.readBits(2)); // reserved zero 2 bits 367 int levelIdc = bitArray.readBits(8); 368 369 int profile = -1; 370 if (constraintSet0Flag == 1 || profileIdc == 66) { 371 profile = constraintSet1Flag == 1 ? AVCProfileConstrainedBaseline : 372 AVCProfileBaseline; 373 } else if (constraintSet1Flag == 1 || profileIdc == 77) { 374 profile = AVCProfileMain; 375 } else if (constraintSet2Flag == 1 || profileIdc == 88) { 376 profile = AVCProfileExtended; 377 } else if (profileIdc == 100) { 378 profile = (constraintSet4Flag == 1 && constraintSet5Flag == 1) 379 ? AVCProfileConstrainedHigh : AVCProfileHigh; 380 } else if (profileIdc == 110) { 381 profile = AVCProfileHigh10; 382 } else if (profileIdc == 122) { 383 profile = AVCProfileHigh422; 384 } else if (profileIdc == 244) { 385 profile = AVCProfileHigh444; 386 } 387 388 // In bitstreams conforming to the Baseline, Constrained Baseline, Main, or 389 // Extended profiles : 390 // - If level_idc is equal to 11 and constraint_set3_flag is equal to 1, the 391 // indicated level is level 1b. 392 // - Otherwise (level_idc is not equal to 11 or constraint_set3_flag is not 393 // equal to 1), level_idc is equal to a value of ten times the level number 394 // (of the indicated level) specified in Table A-1. 395 int level; 396 if ((levelIdc == 11) && (profile == AVCProfileBaseline 397 || profile == AVCProfileConstrainedBaseline || profile == AVCProfileMain 398 || profile == AVCProfileExtended)) { 399 level = constraintSet3Flag == 1 ? AVCLevel1b : AVCLevel11; 400 } else if ((levelIdc == 9) && (profile == AVCProfileHigh 401 || profile == AVCProfileHigh10 || profile == AVCProfileHigh422 402 || profile == AVCProfileHigh444)) { 403 // In bitstreams conforming to the High, High 10, High 4:2:2, High 4:4:4 404 // Predictive, High 10 Intra, High 4:2:2 Intra, High 4:4:4 Intra, or 405 // CAVLC 4:4:4 Intra profiles, 406 // - If level_idc is equal to 9, the indicated level is level 1b. 407 // - Otherwise (level_idc is not equal to 9), level_idc is equal to a 408 // value of ten times the level number (of the indicated level) specified 409 // in Table A-1 410 level = AVCLevel1b; 411 } else { 412 level = getHashMapVal(LEVEL_MAP, levelIdc); 413 } 414 return plToPair(profile, level); 415 } 416 pos = offset; 417 } 418 return null; 419 } 420 } 421 422 static class HevcParser extends ParserBase { 423 private static final int NO_NAL_UNIT_FOUND = -1; 424 private static final int TRAIL_N = 0; 425 private static final int RASL_R = 9; 426 private static final int BLA_W_LP = 16; 427 private static final int RSV_IRAP_VCL23 = 23; 428 private static final HashMap<Integer, Integer> LEVEL_MAP_MAIN_TIER = new HashMap<>() { 429 { 430 put(30, HEVCMainTierLevel1); 431 put(60, HEVCMainTierLevel2); 432 put(63, HEVCMainTierLevel21); 433 put(90, HEVCMainTierLevel3); 434 put(93, HEVCMainTierLevel31); 435 put(120, HEVCMainTierLevel4); 436 put(123, HEVCMainTierLevel41); 437 put(150, HEVCMainTierLevel5); 438 put(153, HEVCMainTierLevel51); 439 put(156, HEVCMainTierLevel52); 440 put(180, HEVCMainTierLevel6); 441 put(183, HEVCMainTierLevel61); 442 put(186, HEVCMainTierLevel62); 443 } 444 }; 445 private static final HashMap<Integer, Integer> LEVEL_MAP_HIGH_TIER = new HashMap<>() { 446 { 447 put(120, HEVCHighTierLevel4); 448 put(123, HEVCHighTierLevel41); 449 put(150, HEVCHighTierLevel5); 450 put(153, HEVCHighTierLevel51); 451 put(156, HEVCHighTierLevel52); 452 put(180, HEVCHighTierLevel6); 453 put(183, HEVCHighTierLevel61); 454 put(186, HEVCHighTierLevel62); 455 } 456 }; 457 getNalUnitStartOffset(byte[] dataArray, int start, int limit)458 private int getNalUnitStartOffset(byte[] dataArray, int start, int limit) { 459 for (int pos = start; pos + 3 < limit; pos++) { 460 if ((pos + 3) < limit && dataArray[pos] == 0 && dataArray[pos + 1] == 0 461 && dataArray[pos + 2] == 1) { 462 return pos + 3; 463 } else if ((pos + 4) < limit && dataArray[pos] == 0 && dataArray[pos + 1] == 0 464 && dataArray[pos + 2] == 0 && dataArray[pos + 3] == 1) { 465 return pos + 4; 466 } 467 } 468 return NO_NAL_UNIT_FOUND; 469 } 470 getNalUnitType(byte[] dataArray, int nalUnitOffset)471 private static int getNalUnitType(byte[] dataArray, int nalUnitOffset) { 472 return (dataArray[nalUnitOffset] & 0x7E) >> 1; 473 } 474 475 @Override getFrameType()476 public int getFrameType() { 477 for (int pos = mOffset; pos < mLimit; ) { 478 int offset = getNalUnitStartOffset(mData, pos, mLimit); 479 if (offset == NO_NAL_UNIT_FOUND) return PICTURE_TYPE_UNKNOWN; 480 int nalUnitType = getNalUnitType(mData, offset); 481 if ((nalUnitType >= TRAIL_N && nalUnitType <= RASL_R) || (nalUnitType >= BLA_W_LP 482 && nalUnitType <= RSV_IRAP_VCL23)) { // codec slice 483 NalParsableBitArray bitArray = new NalParsableBitArray(mData, offset, mLimit); 484 bitArray.readBits(16); // nal_unit_header 485 486 // Parsing slice_segment_header values from H.265/HEVC Table 7.3.6.1 487 boolean firstSliceSegmentInPicFlag = bitArray.readBit(); 488 if (!firstSliceSegmentInPicFlag) return PICTURE_TYPE_UNKNOWN; 489 if (nalUnitType >= BLA_W_LP && nalUnitType <= RSV_IRAP_VCL23) { 490 bitArray.readBit(); // no_output_of_prior_pics_flag 491 } 492 bitArray.readUEV(); // slice_pic_parameter_set_id 493 // FIXME: Assumes num_extra_slice_header_bits element of PPS data to be 0 494 int sliceType = bitArray.readUEV(); 495 if (sliceType == 0) { 496 return PICTURE_TYPE_B; 497 } else if (sliceType == 1) { 498 return PICTURE_TYPE_P; 499 } else if (sliceType == 2) { 500 return PICTURE_TYPE_I; 501 } else { 502 return PICTURE_TYPE_UNKNOWN; 503 } 504 } 505 pos = offset; 506 } 507 return PICTURE_TYPE_UNKNOWN; 508 } 509 510 @Override getProfileLevel(@uppressWarnings"unused") boolean isCsd)511 public Pair<Integer, Integer> getProfileLevel(@SuppressWarnings("unused") boolean isCsd) { 512 for (int pos = mOffset; pos < mLimit; ) { 513 int offset = getNalUnitStartOffset(mData, pos, mLimit); 514 if (offset == NO_NAL_UNIT_FOUND) return null; 515 if (getNalUnitType(mData, offset) == 33) { // sps_nut 516 NalParsableBitArray bitArray = new NalParsableBitArray(mData, offset, mLimit); 517 bitArray.readBits(16); // nal unit header 518 bitArray.readBits(4); // sps video parameter set id 519 bitArray.readBits(3); // sps_max_sub_layers_minus1 520 bitArray.readBits(1); // sps temporal id nesting flag 521 // profile_tier_level 522 bitArray.readBits(2); // generalProfileSpace 523 int generalTierFlag = bitArray.readBits(1); 524 int generalProfileIdc = bitArray.readBits(5); 525 int[] generalProfileCompatibility = new int[32]; 526 for (int j = 0; j < generalProfileCompatibility.length; j++) { 527 generalProfileCompatibility[j] = bitArray.readBits(1); 528 } 529 bitArray.readBits(1); // general progressive source flag 530 bitArray.readBits(1); // general interlaced source flag 531 bitArray.readBits(1); // general non packed constraint flag 532 bitArray.readBits(1); // general frame only constraint flag 533 534 // interpretation of next 44 bits is dependent on generalProfileIdc and 535 // generalProfileCompatibility; but we do not use them in this validation 536 // process, so we're skipping over them. 537 bitArray.readBitsLong(44); 538 int generalLevelIdc = bitArray.readBits(8); 539 540 int profile = -1; 541 if (generalProfileIdc == 1 || generalProfileCompatibility[1] == 1) { 542 profile = HEVCProfileMain; 543 } else if (generalProfileIdc == 2 || generalProfileCompatibility[2] == 1) { 544 profile = HEVCProfileMain10; 545 } else if (generalProfileIdc == 3 || generalProfileCompatibility[3] == 1) { 546 profile = HEVCProfileMainStill; 547 } 548 549 return plToPair(profile, getHashMapVal( 550 generalTierFlag == 0 ? LEVEL_MAP_MAIN_TIER : LEVEL_MAP_HIGH_TIER, 551 generalLevelIdc)); 552 } 553 pos = offset; 554 } 555 return null; 556 } 557 } 558 559 static class Vp9Parser extends ParserBase { 560 private static final HashMap<Integer, Integer> PROFILE_MAP = new HashMap<>() { 561 { 562 put(0, VP9Profile0); 563 put(1, VP9Profile1); 564 put(2, VP9Profile2); 565 put(3, VP9Profile3); 566 } 567 }; 568 private static final HashMap<Integer, Integer> LEVEL_MAP = new HashMap<>() { 569 { 570 put(10, VP9Level1); 571 put(11, VP9Level11); 572 put(20, VP9Level2); 573 put(21, VP9Level21); 574 put(30, VP9Level3); 575 put(31, VP9Level31); 576 put(40, VP9Level4); 577 put(41, VP9Level41); 578 put(50, VP9Level5); 579 put(51, VP9Level51); 580 put(60, VP9Level6); 581 put(61, VP9Level61); 582 put(62, VP9Level62); 583 } 584 }; 585 getProfileLevelFromCSD()586 private Pair<Integer, Integer> getProfileLevelFromCSD() { // parse vp9 codecprivate 587 int profile = -1, level = -1; 588 for (int pos = mOffset; pos < mLimit; ) { 589 ParsableBitArray bitArray = new ParsableBitArray(mData, pos + mOffset, mLimit); 590 int id = bitArray.readBits(8); 591 int len = bitArray.readBits(8); 592 pos += 2; 593 int val = bitArray.readBits(len * 8); 594 pos += len; 595 if (id == 1 || id == 2) { 596 Assert.assertEquals(1, len); 597 if (id == 1) profile = val; 598 else level = val; 599 } 600 if (profile != -1 && level != -1) break; 601 } 602 return plToPair(getHashMapVal(PROFILE_MAP, profile), getHashMapVal(LEVEL_MAP, level)); 603 } 604 getProfileFromFrameHeader()605 private Pair<Integer, Integer> getProfileFromFrameHeader() { // parse uncompressed header 606 ParsableBitArray bitArray = new ParsableBitArray(mData, mOffset, mLimit); 607 bitArray.readBits(2); // frame marker 608 int profileLBit = bitArray.readBits(1); 609 int profileHBit = bitArray.readBits(1); 610 int profile = profileHBit << 1 + profileLBit; 611 return plToPair(getHashMapVal(PROFILE_MAP, profile), -1); 612 } 613 614 @Override getFrameType()615 public int getFrameType() { 616 return PICTURE_TYPE_UNKNOWN; 617 } 618 619 @Override getProfileLevel(boolean isCsd)620 public Pair<Integer, Integer> getProfileLevel(boolean isCsd) { 621 return isCsd ? getProfileLevelFromCSD() : getProfileFromFrameHeader(); 622 } 623 } 624 625 static class Av1Parser extends ParserBase { 626 private static final int OBU_SEQUENCE_HEADER = 1; 627 private static final int OBU_FRAME_HEADER = 3; 628 private static final int OBU_FRAME = 6; 629 private static final int OBP_KEY_FRAME = 0; 630 private static final int OBP_INTER_FRAME = 1; 631 private static final int OBP_INTRA_ONLY_FRAME = 2; 632 private static final int OBP_SWITCH_FRAME = 3; 633 private static final int NUM_REF_FRAMES = 8; 634 635 static class ObuInfo { 636 private int mObuType; 637 private int mTemporalId; 638 private int mSpatialId; 639 private int mHeaderSize; 640 private int mSizeFieldSize; 641 private int mDataSize; 642 getTotalObuSize()643 int getTotalObuSize() { 644 return mHeaderSize + mSizeFieldSize + mDataSize; 645 } 646 getObuDataOffset()647 int getObuDataOffset() { 648 return mHeaderSize + mSizeFieldSize; 649 } 650 } 651 652 static class SeqHeaderObu { 653 public int seqProfile; 654 public int reducedStillPictureHeader; 655 public final int[] seqLevelIdx = new int[32]; 656 public int timingInfoPresentFlag; 657 public int equalPictureInterval; 658 public int decoderModelInfoPresentFlag; 659 public int bufferDelayLengthMinus1; 660 public int bufferRemovalTimeLengthMinus1; 661 public int framePresentationTimeLengthMinus1; 662 public int initialDisplayDelayPresentFlag; 663 public int operatingPointsCntMinus1; 664 public final int[] operatingPointIdc = new int[32]; 665 public final int[] seqTier = new int[32]; 666 public final int[] decoderModelPresentForThisOp = new int[32]; 667 public int frameIdNumbersPresentFlag; 668 public int deltaFrameIdLengthMinus2; 669 public int additionalFrameIdLengthMinus1; 670 public int seqForceScreenContentTools; 671 public int seqForceIntegerMv; 672 public int orderHintBits; 673 public int enableHighBitDepth; 674 } 675 676 static class FrameHeaderObu { 677 public int frameType; 678 public int showFrame; 679 public int showExistingFrame; 680 public int errorResilientMode; 681 } 682 683 private final SeqHeaderObu mSeqHeader = new SeqHeaderObu(); 684 private final int[] mRefFrameType = new int[NUM_REF_FRAMES]; 685 686 // obu header size, obu size field, obu size parseObuHeader(byte[] dataArray, int pos, int limit)687 private ObuInfo parseObuHeader(byte[] dataArray, int pos, int limit) { 688 int obuHeaderSize = 1; 689 ObuInfo obuDetails = new ObuInfo(); 690 ObuParsableBitArray bitArray = new ObuParsableBitArray(dataArray, pos, limit); 691 bitArray.readBits(1); // forbidden bit 692 obuDetails.mObuType = bitArray.readBits(4); // obu type 693 int extensionFlag = bitArray.readBits(1); 694 int hasSizeField = bitArray.readBits(1); 695 Assert.assertEquals(1, hasSizeField); 696 bitArray.readBits(1); // reserved 1bit 697 if (extensionFlag == 1) { 698 obuDetails.mTemporalId = bitArray.readBits(3); 699 obuDetails.mSpatialId = bitArray.readBits(2); 700 bitArray.readBits(3); 701 obuHeaderSize++; 702 } 703 int[] obuSizeInfo = bitArray.leb128(); 704 obuDetails.mHeaderSize = obuHeaderSize; 705 obuDetails.mSizeFieldSize = obuSizeInfo[0]; 706 obuDetails.mDataSize = obuSizeInfo[1]; 707 return obuDetails; 708 } 709 parseSequenceHeader(ObuParsableBitArray bitArray)710 private void parseSequenceHeader(ObuParsableBitArray bitArray) { 711 mSeqHeader.seqProfile = bitArray.readBits(3); 712 bitArray.readBits(1); // still picture 713 mSeqHeader.reducedStillPictureHeader = bitArray.readBits(1); 714 if (mSeqHeader.reducedStillPictureHeader == 1) { 715 mSeqHeader.seqLevelIdx[0] = bitArray.readBits(5); 716 } else { 717 mSeqHeader.timingInfoPresentFlag = bitArray.readBits(1); 718 if (mSeqHeader.timingInfoPresentFlag == 1) { 719 bitArray.readBitsLong(32); // num_units_in_display_tick 720 bitArray.readBitsLong(32); // time_scale 721 mSeqHeader.equalPictureInterval = bitArray.readBits(1); 722 if (mSeqHeader.equalPictureInterval == 1) { 723 bitArray.uvlc(); // num_ticks_per_picture_minus_1 724 } 725 mSeqHeader.decoderModelInfoPresentFlag = bitArray.readBits(1); 726 if (mSeqHeader.decoderModelInfoPresentFlag == 1) { 727 mSeqHeader.bufferDelayLengthMinus1 = bitArray.readBits(5); 728 bitArray.readBitsLong(32); // num_units_in_decoding_tick 729 mSeqHeader.bufferRemovalTimeLengthMinus1 = bitArray.readBits(5); 730 mSeqHeader.framePresentationTimeLengthMinus1 = bitArray.readBits(5); 731 } 732 } else { 733 mSeqHeader.decoderModelInfoPresentFlag = 0; 734 } 735 mSeqHeader.initialDisplayDelayPresentFlag = bitArray.readBits(1); 736 mSeqHeader.operatingPointsCntMinus1 = bitArray.readBits(5); 737 for (int i = 0; i <= mSeqHeader.operatingPointsCntMinus1; i++) { 738 mSeqHeader.operatingPointIdc[i] = bitArray.readBits(12); 739 mSeqHeader.seqLevelIdx[i] = bitArray.readBits(5); 740 if (mSeqHeader.seqLevelIdx[i] > 7) { 741 mSeqHeader.seqTier[i] = bitArray.readBits(1); 742 } 743 if (mSeqHeader.decoderModelInfoPresentFlag == 1) { 744 mSeqHeader.decoderModelPresentForThisOp[i] = bitArray.readBits(1); 745 if (mSeqHeader.decoderModelPresentForThisOp[i] == 1) { 746 int n = mSeqHeader.bufferDelayLengthMinus1 + 1; 747 bitArray.readBits(n); // decoder_buffer_delay 748 bitArray.readBits(n); // encoder_buffer_delay 749 bitArray.readBits(1); // low_delay_mode_flag 750 } 751 } else { 752 mSeqHeader.decoderModelPresentForThisOp[i] = 0; 753 } 754 if (mSeqHeader.initialDisplayDelayPresentFlag == 1) { 755 if (bitArray.readBits(1) == 1) { 756 bitArray.readBits(4); // initial_display_delay_minus_1 757 } 758 } 759 } 760 } 761 int frameWidthBitsMinus1 = bitArray.readBits(4); 762 int frameHeightBitsMinus1 = bitArray.readBits(4); 763 bitArray.readBits(frameWidthBitsMinus1 + 1); // max_frame_width_minus_1 764 bitArray.readBits(frameHeightBitsMinus1 + 1); // max_frame_height_minus_1 765 if (mSeqHeader.reducedStillPictureHeader == 1) { 766 mSeqHeader.frameIdNumbersPresentFlag = 0; 767 } else { 768 mSeqHeader.frameIdNumbersPresentFlag = bitArray.readBits(1); 769 } 770 if (mSeqHeader.frameIdNumbersPresentFlag == 1) { 771 mSeqHeader.deltaFrameIdLengthMinus2 = bitArray.readBits(4); 772 mSeqHeader.additionalFrameIdLengthMinus1 = bitArray.readBits(3); 773 } 774 bitArray.readBits(1); // use_128x128_superblock 775 bitArray.readBits(1); // enable_filter_intra 776 bitArray.readBits(1); // enable_intra_edge_filter 777 if (mSeqHeader.reducedStillPictureHeader == 1) { 778 mSeqHeader.seqForceScreenContentTools = 2; 779 mSeqHeader.seqForceIntegerMv = 2; 780 mSeqHeader.orderHintBits = 0; 781 } else { 782 bitArray.readBits(1); // enable_interintra_compound 783 bitArray.readBits(1); // enable_masked_compound 784 bitArray.readBits(1); // enable_warped_motion 785 bitArray.readBits(1); // enable_dual_filter 786 int enableOrderHint = bitArray.readBits(1); 787 if (enableOrderHint == 1) { 788 bitArray.readBits(1); // enable_jnt_comp 789 bitArray.readBits(1); // enable_ref_frame_mvs 790 } 791 int seqChooseScreenContentTools = bitArray.readBits(1); 792 if (seqChooseScreenContentTools == 1) { 793 mSeqHeader.seqForceScreenContentTools = 2; 794 } else { 795 mSeqHeader.seqForceScreenContentTools = bitArray.readBits(1); 796 } 797 if (mSeqHeader.seqForceScreenContentTools > 0) { 798 int seqChooseIntegerMv = bitArray.readBits(1); 799 if (seqChooseIntegerMv == 1) { 800 mSeqHeader.seqForceIntegerMv = 2; 801 } else { 802 mSeqHeader.seqForceIntegerMv = bitArray.readBits(1); 803 } 804 } else { 805 mSeqHeader.seqForceIntegerMv = 2; 806 } 807 if (enableOrderHint == 1) { 808 int orderHintBitsMinus1 = bitArray.readBits(3); 809 mSeqHeader.orderHintBits = orderHintBitsMinus1 + 1; 810 } else { 811 mSeqHeader.orderHintBits = 0; 812 } 813 } 814 bitArray.readBits(1); // enable super res 815 bitArray.readBits(1); // enable cdef 816 bitArray.readBits(1); // enable restoration 817 mSeqHeader.enableHighBitDepth = bitArray.readBits(1); 818 } 819 parseFrameHeader(ObuParsableBitArray bitArray, int temporalId, int spatialId)820 private FrameHeaderObu parseFrameHeader(ObuParsableBitArray bitArray, int temporalId, 821 int spatialId) { 822 FrameHeaderObu frameHeader = new FrameHeaderObu(); 823 int idLen = 0; 824 if (mSeqHeader.frameIdNumbersPresentFlag == 1) { 825 idLen = mSeqHeader.additionalFrameIdLengthMinus1 826 + mSeqHeader.deltaFrameIdLengthMinus2 + 3; 827 } 828 int allFrames = (1 << NUM_REF_FRAMES) - 1; 829 int refreshFrameFlags = 0; 830 int frameIsIntra; 831 if (mSeqHeader.reducedStillPictureHeader == 1) { 832 frameHeader.frameType = OBP_KEY_FRAME; 833 frameIsIntra = 1; 834 frameHeader.showFrame = 1; 835 } else { 836 frameHeader.showExistingFrame = bitArray.readBits(1); 837 if (frameHeader.showExistingFrame == 1) { 838 int frameToShowMapIdx = bitArray.readBits(3); 839 if (mSeqHeader.decoderModelInfoPresentFlag == 1 840 && mSeqHeader.equalPictureInterval == 0) { 841 int n = mSeqHeader.framePresentationTimeLengthMinus1 + 1; 842 bitArray.readBits(n); // frame_presentation_time 843 } 844 if (mSeqHeader.frameIdNumbersPresentFlag == 1) { 845 bitArray.readBits(idLen); // display_frame_id 846 } 847 frameHeader.frameType = mRefFrameType[frameToShowMapIdx]; 848 if (frameHeader.frameType == OBP_KEY_FRAME) { 849 refreshFrameFlags = allFrames; 850 } 851 return frameHeader; 852 } 853 frameHeader.frameType = bitArray.readBits(2); 854 frameIsIntra = (frameHeader.frameType == OBP_INTRA_ONLY_FRAME 855 || frameHeader.frameType == OBP_KEY_FRAME) ? 1 : 0; 856 frameHeader.showFrame = bitArray.readBits(1); 857 if (frameHeader.showFrame == 1 && mSeqHeader.decoderModelInfoPresentFlag == 1 858 && mSeqHeader.equalPictureInterval == 0) { 859 int n = mSeqHeader.framePresentationTimeLengthMinus1 + 1; 860 bitArray.readBits(n); // frame_presentation_time 861 } 862 if (frameHeader.showFrame == 0) { 863 bitArray.readBits(1); // showable_frame 864 } 865 if (frameHeader.frameType == OBP_SWITCH_FRAME || ( 866 frameHeader.frameType == OBP_KEY_FRAME && frameHeader.showFrame == 1)) { 867 frameHeader.errorResilientMode = 1; 868 } else { 869 frameHeader.errorResilientMode = bitArray.readBits(1); 870 } 871 } 872 bitArray.readBits(1); // disable_cdf_update 873 int allowScreenContentTools; 874 if (mSeqHeader.seqForceScreenContentTools == 2) { 875 allowScreenContentTools = bitArray.readBits(1); 876 } else { 877 allowScreenContentTools = mSeqHeader.seqForceScreenContentTools; 878 } 879 if (allowScreenContentTools == 1 && mSeqHeader.seqForceIntegerMv == 2) { 880 bitArray.readBits(1); // force_integer_mv 881 } 882 if (mSeqHeader.frameIdNumbersPresentFlag == 1) { 883 bitArray.readBits(idLen); // current_frame_id 884 } 885 if (frameHeader.frameType != OBP_SWITCH_FRAME 886 && mSeqHeader.reducedStillPictureHeader == 0) { 887 bitArray.readBits(1); // frame_size_override_flag 888 } 889 bitArray.readBits(mSeqHeader.orderHintBits); // order_hint 890 if (frameIsIntra != 1 && frameHeader.errorResilientMode == 0) { 891 bitArray.readBits(3); // primary_ref_frame 892 } 893 894 if (mSeqHeader.decoderModelInfoPresentFlag == 1) { 895 int bufferRemovalTimePresentFlag = bitArray.readBits(1); 896 if (bufferRemovalTimePresentFlag == 1) { 897 for (int i = 0; i <= mSeqHeader.operatingPointsCntMinus1; i++) { 898 if (mSeqHeader.decoderModelPresentForThisOp[i] == 1) { 899 int opPtIdc = mSeqHeader.operatingPointIdc[i]; 900 boolean inTemporalLayer = ((opPtIdc >> temporalId) & 1) == 1; 901 boolean inSpatialLayer = ((opPtIdc >> (spatialId + 8)) & 1) == 1; 902 if (opPtIdc == 0 || (inTemporalLayer && inSpatialLayer)) { 903 int n = mSeqHeader.bufferRemovalTimeLengthMinus1 + 1; 904 bitArray.readBits(n); // buffer_removal_time 905 } 906 } 907 } 908 } 909 } 910 911 if (frameHeader.frameType == OBP_SWITCH_FRAME || (frameHeader.frameType == OBP_KEY_FRAME 912 && frameHeader.showFrame == 1)) { 913 refreshFrameFlags = allFrames; 914 } else { 915 refreshFrameFlags = bitArray.readBits(8); 916 } 917 918 for (int i = 0; i < 8; i++) { 919 if ((refreshFrameFlags >> i & 1) == 1) { 920 mRefFrameType[i] = frameHeader.frameType; 921 } 922 } 923 return frameHeader; 924 } 925 926 // parse av1 codec configuration record getProfileLevelFromCSD()927 private Pair<Integer, Integer> getProfileLevelFromCSD() { 928 int profile = -1; 929 ParsableBitArray bitArray = new ParsableBitArray(mData, mOffset, mLimit); 930 Assert.assertEquals(1, bitArray.readBits(1)); // marker 931 Assert.assertEquals(1, bitArray.readBits(7)); // version 932 int seqProfile = bitArray.readBits(3); 933 int seqLevelIdx0 = bitArray.readBits(5); 934 bitArray.readBits(1); // seqTier0 935 int highBitDepth = bitArray.readBits(1); 936 bitArray.readBits(1); // is input 12 bit; 937 if (seqProfile == 0) { 938 profile = highBitDepth == 0 ? AV1ProfileMain8 : AV1ProfileMain10; 939 } 940 941 int level = AV1Level2 << seqLevelIdx0; 942 return plToPair(profile, level); 943 } 944 945 // parse av1 sequence header getProfileLevelFromSeqHeader()946 private Pair<Integer, Integer> getProfileLevelFromSeqHeader() { 947 for (int pos = mOffset; pos < mLimit; ) { 948 ObuInfo obuDetails = parseObuHeader(mData, pos, mLimit); 949 ObuParsableBitArray bitArray = 950 new ObuParsableBitArray(mData, pos + obuDetails.getObuDataOffset(), 951 pos + obuDetails.getTotalObuSize()); 952 if (obuDetails.mObuType == OBU_SEQUENCE_HEADER) { 953 int profile = -1; 954 parseSequenceHeader(bitArray); 955 if (mSeqHeader.seqProfile == 0) { 956 profile = mSeqHeader.enableHighBitDepth == 0 ? AV1ProfileMain8 : 957 AV1ProfileMain10; 958 } 959 960 int level = AV1Level2 << mSeqHeader.seqLevelIdx[0]; 961 return plToPair(profile, level); 962 } 963 pos += obuDetails.getTotalObuSize(); 964 } 965 return null; 966 } 967 968 @Override getFrameType()969 public int getFrameType() { 970 ArrayList<FrameHeaderObu> headers = new ArrayList<>(); 971 for (int pos = mOffset; pos < mLimit; ) { 972 ObuInfo obuDetails = parseObuHeader(mData, pos, mLimit); 973 ObuParsableBitArray bitArray = 974 new ObuParsableBitArray(mData, pos + obuDetails.getObuDataOffset(), 975 pos + obuDetails.getTotalObuSize()); 976 if (obuDetails.mObuType == OBU_SEQUENCE_HEADER) { 977 parseSequenceHeader(bitArray); 978 } else if (obuDetails.mObuType == OBU_FRAME_HEADER 979 || obuDetails.mObuType == OBU_FRAME) { 980 FrameHeaderObu frameHeader = parseFrameHeader(bitArray, obuDetails.mTemporalId, 981 obuDetails.mSpatialId); 982 headers.add(frameHeader); 983 } 984 pos += obuDetails.getTotalObuSize(); 985 } 986 for (FrameHeaderObu frameHeader : headers) { 987 if (frameHeader.showFrame == 1 || frameHeader.showExistingFrame == 1) { 988 if (frameHeader.frameType == OBP_KEY_FRAME 989 || frameHeader.frameType == OBP_INTRA_ONLY_FRAME) { 990 return PICTURE_TYPE_I; 991 } else if (frameHeader.frameType == OBP_INTER_FRAME) { 992 return PICTURE_TYPE_P; 993 } 994 return PICTURE_TYPE_UNKNOWN; 995 } 996 } 997 return PICTURE_TYPE_UNKNOWN; 998 } 999 1000 @Override getProfileLevel(boolean isCsd)1001 public Pair<Integer, Integer> getProfileLevel(boolean isCsd) { 1002 return isCsd ? getProfileLevelFromCSD() : getProfileLevelFromSeqHeader(); 1003 } 1004 } 1005 1006 static class AacParser extends ParserBase { 1007 @Override getFrameType()1008 public int getFrameType() { 1009 return PICTURE_TYPE_UNKNOWN; 1010 } 1011 1012 @Override getProfileLevel(@uppressWarnings"unused") boolean isCsd)1013 public Pair<Integer, Integer> getProfileLevel(@SuppressWarnings("unused") boolean isCsd) { 1014 // parse AudioSpecificConfig() of ISO 14496 Part 3 1015 ParsableBitArray bitArray = new ParsableBitArray(mData, mOffset, mLimit); 1016 int audioObjectType = bitArray.readBits(5); 1017 if (audioObjectType == 31) { 1018 audioObjectType = 32 + bitArray.readBits(6); // audio object type ext 1019 } 1020 return plToPair(audioObjectType, -1); 1021 } 1022 } 1023 getParserObject(String mediaType)1024 public static ParserBase getParserObject(String mediaType) { 1025 switch (mediaType) { 1026 case MediaFormat.MIMETYPE_VIDEO_MPEG4: 1027 return new Mpeg4Parser(); 1028 case MediaFormat.MIMETYPE_VIDEO_H263: 1029 return new H263Parser(); 1030 case MediaFormat.MIMETYPE_VIDEO_AVC: 1031 return new AvcParser(); 1032 case MediaFormat.MIMETYPE_VIDEO_HEVC: 1033 return new HevcParser(); 1034 case MediaFormat.MIMETYPE_VIDEO_AV1: 1035 return new Av1Parser(); 1036 case MediaFormat.MIMETYPE_VIDEO_VP9: 1037 return new Vp9Parser(); 1038 case MediaFormat.MIMETYPE_AUDIO_AAC: 1039 return new AacParser(); 1040 } 1041 return null; 1042 } 1043 getFrameTypeFromBitStream(ByteBuffer buf, MediaCodec.BufferInfo info, ParserBase o)1044 public static int getFrameTypeFromBitStream(ByteBuffer buf, MediaCodec.BufferInfo info, 1045 ParserBase o) { 1046 if (o == null) return PICTURE_TYPE_UNKNOWN; 1047 byte[] dataArray = new byte[info.size]; 1048 buf.position(info.offset); 1049 buf.get(dataArray); 1050 o.set(dataArray, 0, info.size); 1051 return o.getFrameType(); 1052 } 1053 getProfileLevelFromBitStream(ByteBuffer buf, MediaCodec.BufferInfo info, ParserBase o)1054 public static Pair<Integer, Integer> getProfileLevelFromBitStream(ByteBuffer buf, 1055 MediaCodec.BufferInfo info, ParserBase o) { 1056 if (o == null) return null; 1057 byte[] dataArray = new byte[info.size]; 1058 buf.position(info.offset); 1059 buf.get(dataArray); 1060 o.set(dataArray, 0, info.size); 1061 return o.getProfileLevel((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0); 1062 } 1063 } 1064