/*===========================================================================* * block.c * * Block routines * * NOTES: MAD = Mean Absolute Difference *===========================================================================*/ /* Copyright information is at end of file */ #include #include "pm_c_util.h" #include "all.h" #include "mtypes.h" #include "frames.h" #include "bitio.h" #include "prototypes.h" #include "fsize.h" #include "opts.h" #include "postdct.h" #include "block.h" #define TRUNCATE_UINT8(x) ((x < 0) ? 0 : ((x > 255) ? 255 : x)) /*==================* * GLOBAL VARIABLES * *==================*/ extern Block **dct, **dctb, **dctr; static vector halfVector(vector const v) { vector half; half.y = v.y/2; half.x = v.x/2; return half; } /*===========================* * COMPUTE DCT OF DIFFERENCE * *===========================*/ /*===========================================================================* * * compute current-motionBlock, take the DCT, and put the difference * back into current * * RETURNS: current block modified * * SIDE EFFECTS: none * *===========================================================================*/ void ComputeDiffDCTBlock(Block current, Block dest, Block motionBlock, boolean * const significantDifferenceP) { unsigned int y; int diff; diff = 0; /* initial value */ for (y = 0; y < 8; ++y) { unsigned int x; for (x = 0; x < 8; ++x) { current[y][x] -= motionBlock[y][x]; diff += ABS(current[y][x]); } } /* Kill the block if change is too small */ /* (block_bound defaults to 128, see opts.c) */ if (diff < block_bound) *significantDifferenceP = FALSE; else { mp_fwd_dct_block2(current, dest); *significantDifferenceP = TRUE; } } /*===========================================================================* * * appropriate (according to pattern, the coded block pattern) blocks * of 'current' are diff'ed and DCT'd. * * RETURNS: current blocks modified * * SIDE EFFECTS: Can remove too-small difference blocks from pattern * * PRECONDITIONS: appropriate blocks of 'current' have not yet been * modified * *===========================================================================*/ void ComputeDiffDCTs(MpegFrame * const current, MpegFrame * const prev, int const by, int const bx, vector const m, int * const patternP) { Block motionBlock; if (collect_quant && (collect_quant_detailed & 1)) fprintf(collect_quant_fp, "l\n"); if (*patternP & 0x20) { boolean significantDiff; ComputeMotionBlock(prev->ref_y, by, bx, m, &motionBlock); ComputeDiffDCTBlock(current->y_blocks[by][bx], dct[by][bx], motionBlock, &significantDiff); if (!significantDiff) *patternP ^= 0x20; } if (*patternP & 0x10) { boolean significantDiff; ComputeMotionBlock(prev->ref_y, by, bx+1, m, &motionBlock); ComputeDiffDCTBlock(current->y_blocks[by][bx+1], dct[by][bx+1], motionBlock, &significantDiff); if (!significantDiff) *patternP ^= 0x10; } if (*patternP & 0x8) { boolean significantDiff; ComputeMotionBlock(prev->ref_y, by+1, bx, m, &motionBlock); ComputeDiffDCTBlock(current->y_blocks[by+1][bx], dct[by+1][bx], motionBlock, &significantDiff); if (!significantDiff) *patternP ^= 0x8; } if (*patternP & 0x4) { boolean significantDiff; ComputeMotionBlock(prev->ref_y, by+1, bx+1, m, &motionBlock); ComputeDiffDCTBlock(current->y_blocks[by+1][bx+1], dct[by+1][bx+1], motionBlock, &significantDiff); if (!significantDiff) *patternP ^= 0x4; } if (collect_quant && (collect_quant_detailed & 1)) fprintf(collect_quant_fp, "c\n"); if (*patternP & 0x2) { boolean significantDiff; ComputeMotionBlock(prev->ref_cb, by/2, bx/2, halfVector(m), &motionBlock); ComputeDiffDCTBlock(current->cb_blocks[by/2][bx/2], dctb[by/2][bx/2], motionBlock, &significantDiff); if (!significantDiff) *patternP ^= 0x2; } if (*patternP & 0x1) { boolean significantDiff; ComputeMotionBlock(prev->ref_cr, by/2, bx/2, halfVector(m), &motionBlock); ComputeDiffDCTBlock(current->cr_blocks[by/2][bx/2], dctr[by/2][bx/2], motionBlock, &significantDiff); if (!significantDiff) *patternP ^= 0x1; } } static void computePrevFyFx(MpegFrame * const prevFrame, int const by, int const bx, vector const m, uint8 *** const prevP, int * const fyP, int * const fxP) { boolean const xHalf = (ABS(m.x) % 2 == 1); boolean const yHalf = (ABS(m.y) % 2 == 1); MotionToFrameCoord(by, bx, m.y/2, m.x/2, fyP, fxP); assert(*fyP >= 0); assert(*fxP >= 0); /* C integer arithmetic rounds toward zero. But what we need is a "floor" -- i.e. round down. So we adjust now for where the dividend in the above divide by two was negative. */ if (xHalf) { if (m.x < 0) --*fxP; if (yHalf) { if (m.y < 0) --*fyP; *prevP = prevFrame->halfBoth; } else *prevP = prevFrame->halfX; } else if (yHalf) { if (m.y < 0) --*fyP; *prevP = prevFrame->halfY; } else *prevP = prevFrame->ref_y; } /*======================* * COMPUTE MOTION BLOCK * *======================*/ /*===========================================================================* * * compute the motion-compensated block * * RETURNS: motionBlock * * SIDE EFFECTS: none * * PRECONDITIONS: motion vector MUST be valid * * NOTE: could try to speed this up using halfX, halfY, halfBoth, * but then would have to compute for chrominance, and it's just * not worth the trouble (this procedure is not called relatively * often -- a constant number of times per macroblock) * *===========================================================================*/ void ComputeMotionBlock(uint8 ** const prev, int const by, int const bx, vector const m, Block * const motionBlockP) { int fy, fx; boolean xHalf, yHalf; xHalf = (ABS(m.x) % 2 == 1); yHalf = (ABS(m.y) % 2 == 1); MotionToFrameCoord(by, bx, m.y/2, m.x/2, &fy, &fx); if (xHalf && yHalf) { unsigned int y; /* really should be fy+y-1 and fy+y so do (fy-1)+y = fy+y-1 and (fy-1)+y+1 = fy+y */ if (m.y < 0) --fy; if (m.x < 0) --fx; for (y = 0; y < 8; ++y) { int16 * const destPtr = (*motionBlockP)[y]; uint8 * const srcPtr = &(prev[fy+y][fx]); uint8 * const srcPtr2 = &(prev[fy+y+1][fx]); unsigned int x; for (x = 0; x < 8; ++x) destPtr[x] = (srcPtr[x]+srcPtr[x+1]+srcPtr2[x]+srcPtr2[x+1]+2) >> 2; } } else if (xHalf) { unsigned int y; if (m.x < 0) --fx; for (y = 0; y < 8; ++y) { int16 * const destPtr = (*motionBlockP)[y]; uint8 * const srcPtr = &(prev[fy+y][fx]); unsigned int x; for (x = 0; x < 8; ++x) destPtr[x] = (srcPtr[x]+srcPtr[x+1]+1) >> 1; } } else if (yHalf) { unsigned int y; if ( m.y < 0 ) fy--; for (y = 0; y < 8; ++y) { int16 * const destPtr = (*motionBlockP)[y]; uint8 * const srcPtr = &(prev[fy+y][fx]); uint8 * const srcPtr2 = &(prev[fy+y+1][fx]); unsigned int x; for (x = 0; x < 8; ++x) destPtr[x] = (srcPtr[x]+srcPtr2[x]+1) >> 1; } } else { unsigned int y; for (y = 0; y < 8; ++y) { int16 * const destPtr = (*motionBlockP)[y]; uint8 * const srcPtr = &(prev[fy+y][fx]); unsigned int x; for (x = 0; x < 8; ++x) destPtr[x] = srcPtr[x]; } } } /*===========================================================================* * * compute the motion-compensated luminance block * * RETURNS: motionBlock * * SIDE EFFECTS: none * * PRECONDITIONS: motion vector MUST be valid * * NOTE: see ComputeMotionBlock * *===========================================================================*/ void ComputeMotionLumBlock(MpegFrame * const prevFrame, int const by, int const bx, vector const m, LumBlock * const motionBlockP) { unsigned int y; uint8 ** prev; int fy, fx; computePrevFyFx(prevFrame, by, bx, m, &prev, &fy, &fx); for (y = 0; y < 16; ++y) { uint8 * const across = &(prev[fy+y][fx]); int32 * const macross = motionBlockP->l[y]; unsigned int x; for (x = 0; x < 16; ++x) macross[x] = across[x]; } /* this is what's really happening, in slow motion: * * for (y = 0; y < 16; ++y, ++py) * for (x = 0; x < 16; ++x, ++px) * motionBlock[y][x] = prev[fy+y][fx+x]; * */ } /*=======================* * BASIC ERROR FUNCTIONS * *=======================*/ /*===========================================================================* * * return the MAD of two luminance blocks * * RETURNS: the MAD, if less than bestSoFar, or some number bigger if not * * SIDE EFFECTS: none * *===========================================================================*/ int32 LumBlockMAD(const LumBlock * const currentBlockP, const LumBlock * const motionBlockP, int32 const bestSoFar) { int32 diff; /* max value of diff is 255*256 = 65280 */ unsigned int y; diff = 0; /* initial value */ for (y = 0; y < 16; ++y) { const int32 * const currentRow = currentBlockP->l[y]; const int32 * const motionRow = motionBlockP->l[y]; unsigned int x; for (x = 0; x < 16; ++x) diff += ABS(currentRow[x] - motionRow[x]); if (diff > bestSoFar) /* We already know the MAD won't be less than bestSoFar; Caller doesn't care by how much we missed, so just return this. */ return diff; } /* Return the actual MAD */ return diff; } /*===========================================================================* * * return the MAD of the currentBlock and the motion-compensated block * (without TUNEing) * * (by, bx) is the location of the block in the frame * (block number coordinates). 'm' is the motion of the block in pixels. * The moved block must be wholly within the frame. * * RETURNS: the MAD, if less than bestSoFar, or * some number bigger if not * * SIDE EFFECTS: none * * PRECONDITIONS: motion vector MUST be valid * * NOTES: this is the procedure that is called the most, and should therefore * be the most optimized!!! * *===========================================================================*/ int32 LumMotionError(const LumBlock * const currentBlockP, MpegFrame * const prevFrame, int const by, int const bx, vector const m, int32 const bestSoFar) { int32 adiff; int32 diff; /* max value of diff is 255*256 = 65280 */ int y; uint8 **prev; int fy, fx; computePrevFyFx(prevFrame, by, bx, m, &prev, &fy, &fx); assert(fy >= 0); assert(fx >= 0); adiff = 0; /* initial value */ diff = 0; /* initial value */ switch (SearchCompareMode) { case DEFAULT_SEARCH: /* Default. */ for (y = 0; y < 16; ++y) { const int32 * const cacross = currentBlockP->l[y]; uint8 * const across = &prev[fy+y][fx]; unsigned int x; for (x = 0; x < 16; ++x) { int32 const localDiff = across[x]-cacross[x]; diff += ABS(localDiff); } if (diff > bestSoFar) return diff; } break; case LOCAL_DCT: { Block dctdiff[4], dctquant[4]; FlatBlock quant; int x, i, tmp; int distortion=0, datarate=0; int pq = GetPQScale(); for (y = 0; y < 16; ++y) { const int32 * const cacross = currentBlockP->l[y]; uint8 * const across = &(prev[fy+y][fx]); for (x = 0; x < 16; ++x) { dctdiff[(x>7)+2*(y>7)][y%8][x%8] = cacross[x]-across[x]; }} /* Calculate rate */ for (i = 0; i < 4; ++i) { mp_fwd_dct_block2(dctdiff[i], dctdiff[i]); if (Mpost_QuantZigBlock(dctdiff[i], quant, pq, FALSE) == MPOST_ZERO) { /* no sense in continuing */ memset((char *)dctquant[i], 0, sizeof(Block)); } else { Mpost_UnQuantZigBlock(quant, dctquant[i], pq, FALSE); mpeg_jrevdct((int16 *)dctquant[i]); datarate += CalcRLEHuffLength(quant); } } /* Calculate distortion */ for (y = 0; y < 16; ++y) { const int32 * const cacross = currentBlockP->l[y]; uint8 * const across = &(prev[fy+y][fx]); for (x = 0; x < 16; ++x) { tmp = across[x] - cacross[x] + dctquant[(x>7)+2*(y>7)][y%8][x%8]; distortion += tmp*tmp; }} distortion /= 256; distortion *= LocalDCTDistortScale; datarate *= LocalDCTRateScale; diff = (int) sqrt(distortion*distortion + datarate*datarate); break; } case NO_DC_SEARCH: { extern int32 niqtable[]; int pq = niqtable[0]*GetPQScale(); unsigned int y; for (y = 0; y < 16; ++y) { const int32 * const cacross = currentBlockP->l[y]; uint8 * const across = &(prev[fy+y][fx]); unsigned int x; for (x = 0; x < 16; ++x) { int32 const localDiff = across[x]-cacross[x]; diff += localDiff; adiff += ABS(localDiff); } } diff /= 64*pq; /* diff is now the DC difference (with QSCALE 1) */ adiff -= 64*pq*ABS(diff); diff = adiff; } break; case DO_Mean_Squared_Distortion: for (y = 0; y < 16; ++y) { const int32 * const cacross = currentBlockP->l[y]; uint8 * const across = &(prev[fy+y][fx]); unsigned int x; for (x = 0; x < 16; ++x) { int32 const localDiff = across[x] - cacross[x]; diff += localDiff * localDiff; } if (diff > bestSoFar) return diff; } break; } /* End of Switch */ return diff; } /*===========================================================================* * * return the MAD of the currentBlock and the average of the blockSoFar * and the motion-compensated block (this is used for B-frame searches) * * RETURNS: the MAD, if less than bestSoFar, or * some number bigger if not * * SIDE EFFECTS: none * * PRECONDITIONS: motion vector MUST be valid * *===========================================================================*/ int32 LumAddMotionError(const LumBlock * const currentBlockP, const LumBlock * const blockSoFarP, MpegFrame * const prevFrame, int const by, int const bx, vector const m, int32 const bestSoFar) { int32 diff; /* max value of diff is 255*256 = 65280 */ int y; uint8 **prev; int fy, fx; computePrevFyFx(prevFrame, by, bx, m, &prev, &fy, &fx); diff = 0; /* initial value */ /* do we add 1 before dividing by two? Yes -- see MPEG-1 doc page 46 */ for (y = 0; y < 16; ++y) { unsigned int x; const uint8 * const across = &prev[fy+y][fx]; const int32 * const bacross = blockSoFarP->l[y]; const int32 * const cacross = currentBlockP->l[y]; for (x = 0; x < 16; ++x) { int32 const localDiff = ((across[x] + bacross[x] + 1) / 2) - cacross[x]; diff += ABS(localDiff); } if (diff > bestSoFar) return diff; } /* This is what's happening: * * ComputeMotionLumBlock(prevFrame, by, bx, my, mx, lumMotionBlock); * * for (y = 0; y < 16; ++y) * for (x = 0; x < 16; ++x) { * localDiff = currentBlock[y][x] - lumMotionBlock[y][x]; * diff += ABS(localDiff); * } */ return diff; } /*===========================================================================* * * adds the motion-compensated block to the given block * * RETURNS: block modified * * SIDE EFFECTS: none * * PRECONDITIONS: motion vector MUST be valid * *===========================================================================*/ void AddMotionBlock(Block block, uint8 ** const prev, int const by, int const bx, vector const m) { int fy, fx; boolean xHalf, yHalf; xHalf = (ABS(m.x) % 2 == 1); yHalf = (ABS(m.y) % 2 == 1); MotionToFrameCoord(by, bx, (m.y/2), (m.x/2), &fy, &fx); if (xHalf && yHalf) { unsigned int y; /* really should be fy+y-1 and fy+y so do (fy-1)+y = fy+y-1 and (fy-1)+y+1 = fy+y */ if (m.y < 0) --fy; if (m.x < 0) --fx; for (y = 0; y < 8; ++y) { unsigned int x; for (x = 0; x < 8; ++x) block[y][x] += (prev[fy+y][fx+x]+prev[fy+y][fx+x+1]+ prev[fy+y+1][fx+x]+prev[fy+y+1][fx+x+1]+2)>>2; } } else if (xHalf) { unsigned int y; if (m.x < 0) --fx; for (y = 0; y < 8; ++y) { unsigned int x; for (x = 0; x < 8; ++x) { block[y][x] += (prev[fy+y][fx+x]+prev[fy+y][fx+x+1]+1)>>1; } } } else if ( yHalf ) { unsigned int y; if (m.y < 0) --fy; for (y = 0; y < 8; ++y) { unsigned int x; for (x = 0; x < 8; ++x) { block[y][x] += (prev[fy+y][fx+x]+prev[fy+y+1][fx+x]+1)>>1; } } } else { unsigned int y; for (y = 0; y < 8; ++y) { unsigned int x; for (x = 0; x < 8; ++x) { block[y][x] += (int16)prev[fy+y][fx+x]; } } } } /*===========================================================================* * * adds the motion-compensated B-frame block to the given block * * RETURNS: block modified * * SIDE EFFECTS: none * * PRECONDITIONS: motion vectors MUST be valid * *===========================================================================*/ void AddBMotionBlock(Block block, uint8 ** const prev, uint8 ** const next, int const by, int const bx, int const mode, motion const motion) { unsigned int y; Block prevBlock, nextBlock; switch (mode) { case MOTION_FORWARD: AddMotionBlock(block, prev, by, bx, motion.fwd); break; case MOTION_BACKWARD: AddMotionBlock(block, next, by, bx, motion.bwd); break; default: ComputeMotionBlock(prev, by, bx, motion.fwd, &prevBlock); ComputeMotionBlock(next, by, bx, motion.bwd, &nextBlock); } for (y = 0; y < 8; ++y) { unsigned int x; for (x = 0; x < 8; ++x) { block[y][x] += (prevBlock[y][x] + nextBlock[y][x] + 1) / 2; } } } /*===========================================================================* * * copies the given block into the appropriate data area * * RETURNS: data modified * * SIDE EFFECTS: none * *===========================================================================*/ void BlockToData(uint8 ** const data, Block block, int const by, int const bx) { int x, y; int fy, fx; int16 blockItem; BLOCK_TO_FRAME_COORD(by, bx, fy, fx); for ( y = 0; y < 8; y++ ) { for ( x = 0; x < 8; x++ ) { blockItem = block[y][x]; data[fy+y][fx+x] = TRUNCATE_UINT8(blockItem); } } } /*===========================================================================* * * copies data into appropriate blocks * * RETURNS: mf modified * * SIDE EFFECTS: none * * NOTES: probably shouldn't be in this file * *===========================================================================*/ void BlockifyFrame(MpegFrame * const frameP) { int dctx, dcty; int x, y; int bx, by; int fy, fx; int16 *destPtr; uint8 *srcPtr; int16 *destPtr2; uint8 *srcPtr2; Block *blockPtr; Block *blockPtr2; dctx = Fsize_x / DCTSIZE; dcty = Fsize_y / DCTSIZE; /* * copy y data into y_blocks */ for (by = 0; by < dcty; by++) { fy = by*DCTSIZE; for (bx = 0; bx < dctx; bx++) { fx = bx*DCTSIZE; blockPtr = (Block *) &(frameP->y_blocks[by][bx][0][0]); for (y = 0; y < DCTSIZE; y++) { destPtr = &((*blockPtr)[y][0]); srcPtr = &(frameP->orig_y[fy+y][fx]); for (x = 0; x < DCTSIZE; x++) { destPtr[x] = srcPtr[x]; } } } } /* * copy cr/cb data into cr/cb_blocks */ for (by = 0; by < (dcty >> 1); by++) { fy = by*DCTSIZE; for (bx = 0; bx < (dctx >> 1); bx++) { fx = bx*DCTSIZE; blockPtr = (Block *) &(frameP->cr_blocks[by][bx][0][0]); blockPtr2 = (Block *) &(frameP->cb_blocks[by][bx][0][0]); for (y = 0; y < DCTSIZE; y++) { destPtr = &((*blockPtr)[y][0]); srcPtr = &(frameP->orig_cr[fy+y][fx]); destPtr2 = &((*blockPtr2)[y][0]); srcPtr2 = &(frameP->orig_cb[fy+y][fx]); for (x = 0; x < DCTSIZE; x++) { destPtr[x] = srcPtr[x]; destPtr2[x] = srcPtr2[x]; } } } } } /*===========================================================================* * * * UNUSED PROCEDURES * * * * The following procedures are all unused by the encoder * * * * They are listed here for your convenience. You might want to use * * them if you experiment with different search techniques * * * *===========================================================================*/ #ifdef UNUSED_PROCEDURES /* this procedure calculates the subsampled motion block (obviously) * * for speed, this procedure is probably not called anywhere (it is * incorporated directly into LumDiffA, LumDiffB, etc. * * but leave it here anyway for clarity * * (startY, startX) = (0,0) for A....(0,1) for B...(1,0) for C...(1,1) for D * */ void ComputeSubSampledMotionLumBlock(MpegFrame * const prevFrame, int const by, int const bx, int const my, int const mx, LumBlock const motionBlock, int const startY, int const startX) { uint8 *across; int32 *macross; int32 *lastx; int y; uint8 **prev; int fy, fx; boolean xHalf, yHalf; xHalf = (ABS(mx) % 2 == 1); yHalf = (ABS(my) % 2 == 1); MotionToFrameCoord(by, bx, my/2, mx/2, &fy, &fx); if ( xHalf ) { if ( mx < 0 ) { fx--; } if ( yHalf ) { if ( my < 0 ) { fy--; } prev = prevFrame->halfBoth; } else { prev = prevFrame->halfX; } } else if ( yHalf ) { if ( my < 0 ) { fy--; } prev = prevFrame->halfY; } else { prev = prevFrame->ref_y; } for ( y = startY; y < 16; y += 2 ) { across = &(prev[fy+y][fx+startX]); macross = &(motionBlock[y][startX]); lastx = &(motionBlock[y][16]); while ( macross < lastx ) { (*macross) = (*across); across += 2; macross += 2; } } /* this is what's really going on in slow motion: * * for ( y = startY; y < 16; y += 2 ) * for ( x = startX; x < 16; x += 2 ) * motionBlock[y][x] = prev[fy+y][fx+x]; * */ } /*===========================================================================* * * return the MAD of the currentBlock and the motion-compensated block, * subsampled 4:1 with given starting coordinates (startY, startX) * * RETURNS: the MAD * * SIDE EFFECTS: none * * PRECONDITIONS: motion vector MUST be valid * * NOTES: this procedure is never called. Instead, see subsample.c. This * procedure is provided only for possible use in extensions * *===========================================================================*/ int32 LumMotionErrorSubSampled(LumBlock const currentBlock, MpegFrame * const prevFrame, int const by, int const bx, int const my, int const mx, int const startY, int const startX) { int32 diff; /* max value of diff is 255*256 = 65280 */ int32 localDiff; int32 *cacross; uint8 *macross; int32 *lastx; int y; uint8 **prev; int fy, fx; boolean xHalf, yHalf; xHalf = (ABS(mx) % 2 == 1); yHalf = (ABS(my) % 2 == 1); motionToFrameCoord(by, bx, my/2, mx/2, &fy, &fx); if ( xHalf ) { if ( mx < 0 ) { fx--; } if ( yHalf ) { if ( my < 0 ) { fy--; } prev = prevFrame->halfBoth; } else { prev = prevFrame->halfX; } } else if ( yHalf ) { if ( my < 0 ) { fy--; } prev = prevFrame->halfY; } else { prev = prevFrame->ref_y; } diff = 0; /* initial value */ for ( y = startY; y < 16; y += 2 ) { macross = &(prev[fy+y][fx+startX]); cacross = &(currentBlock[y][startX]); lastx = &(currentBlock[y][16]); while ( cacross < lastx ) { localDiff = (*cacross)-(*macross); diff += ABS(localDiff); macross += 2; cacross += 2; } } /* this is what's really happening: * * ComputeSubSampledMotionLumBlock(prevFrame, by, bx, my, mx, * lumMotionBlock, startY, startX); * * for ( y = startY; y < 16; y += 2 ) * for ( x = startX; x < 16; x += 2 ) * { * localDiff = currentBlock[y][x] - lumMotionBlock[y][x]; * diff += ABS(localDiff); * } * */ return (int32)diff; } #endif /* UNUSED_PROCEDURES */ /* * Copyright (c) 1995 The Regents of the University of California. * All rights reserved. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without written agreement is * hereby granted, provided that the above copyright notice and the following * two paragraphs appear in all copies of this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. */