1/* ----------------------------------------------------------------------------- 2Software License for The Fraunhofer FDK AAC Codec Library for Android 3 4© Copyright 1995 - 2018 Fraunhofer-Gesellschaft zur Förderung der angewandten 5Forschung e.V. All rights reserved. 6 7 1. INTRODUCTION 8The Fraunhofer FDK AAC Codec Library for Android ("FDK AAC Codec") is software 9that implements the MPEG Advanced Audio Coding ("AAC") encoding and decoding 10scheme for digital audio. This FDK AAC Codec software is intended to be used on 11a wide variety of Android devices. 12 13AAC's HE-AAC and HE-AAC v2 versions are regarded as today's most efficient 14general perceptual audio codecs. AAC-ELD is considered the best-performing 15full-bandwidth communications codec by independent studies and is widely 16deployed. AAC has been standardized by ISO and IEC as part of the MPEG 17specifications. 18 19Patent licenses for necessary patent claims for the FDK AAC Codec (including 20those of Fraunhofer) may be obtained through Via Licensing 21(www.vialicensing.com) or through the respective patent owners individually for 22the purpose of encoding or decoding bit streams in products that are compliant 23with the ISO/IEC MPEG audio standards. Please note that most manufacturers of 24Android devices already license these patent claims through Via Licensing or 25directly from the patent owners, and therefore FDK AAC Codec software may 26already be covered under those patent licenses when it is used for those 27licensed purposes only. 28 29Commercially-licensed AAC software libraries, including floating-point versions 30with enhanced sound quality, are also available from Fraunhofer. Users are 31encouraged to check the Fraunhofer website for additional applications 32information and documentation. 33 342. COPYRIGHT LICENSE 35 36Redistribution and use in source and binary forms, with or without modification, 37are permitted without payment of copyright license fees provided that you 38satisfy the following conditions: 39 40You must retain the complete text of this software license in redistributions of 41the FDK AAC Codec or your modifications thereto in source code form. 42 43You must retain the complete text of this software license in the documentation 44and/or other materials provided with redistributions of the FDK AAC Codec or 45your modifications thereto in binary form. You must make available free of 46charge copies of the complete source code of the FDK AAC Codec and your 47modifications thereto to recipients of copies in binary form. 48 49The name of Fraunhofer may not be used to endorse or promote products derived 50from this library without prior written permission. 51 52You may not charge copyright license fees for anyone to use, copy or distribute 53the FDK AAC Codec software or your modifications thereto. 54 55Your modified versions of the FDK AAC Codec must carry prominent notices stating 56that you changed the software and the date of any change. For modified versions 57of the FDK AAC Codec, the term "Fraunhofer FDK AAC Codec Library for Android" 58must be replaced by the term "Third-Party Modified Version of the Fraunhofer FDK 59AAC Codec Library for Android." 60 613. NO PATENT LICENSE 62 63NO EXPRESS OR IMPLIED LICENSES TO ANY PATENT CLAIMS, including without 64limitation the patents of Fraunhofer, ARE GRANTED BY THIS SOFTWARE LICENSE. 65Fraunhofer provides no warranty of patent non-infringement with respect to this 66software. 67 68You may use this FDK AAC Codec software or modifications thereto only for 69purposes that are authorized by appropriate patent licenses. 70 714. DISCLAIMER 72 73This FDK AAC Codec software is provided by Fraunhofer on behalf of the copyright 74holders and contributors "AS IS" and WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, 75including but not limited to the implied warranties of merchantability and 76fitness for a particular purpose. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 77CONTRIBUTORS BE LIABLE for any direct, indirect, incidental, special, exemplary, 78or consequential damages, including but not limited to procurement of substitute 79goods or services; loss of use, data, or profits, or business interruption, 80however caused and on any theory of liability, whether in contract, strict 81liability, or tort (including negligence), arising in any way out of the use of 82this software, even if advised of the possibility of such damage. 83 845. CONTACT INFORMATION 85 86Fraunhofer Institute for Integrated Circuits IIS 87Attention: Audio and Multimedia Departments - FDK AAC LL 88Am Wolfsmantel 33 8991058 Erlangen, Germany 90 91www.iis.fraunhofer.de/amm 92amm-info@iis.fraunhofer.de 93----------------------------------------------------------------------------- */ 94 95/*********************** MPEG surround encoder library ************************* 96 97 Author(s): Max Neuendorf 98 99 Description: Encoder Library Interface 100 Get windows for framing 101 102*******************************************************************************/ 103 104/**************************************************************************/ /** 105 \file 106 Description of file contents 107 ******************************************************************************/ 108 109/* Includes ******************************************************************/ 110#include "sacenc_framewindowing.h" 111#include "sacenc_vectorfunctions.h" 112 113/* Defines *******************************************************************/ 114 115/* Data Types ****************************************************************/ 116typedef struct T_FRAMEWINDOW { 117 INT nTimeSlotsMax; 118 INT bFrameKeep; 119 INT startSlope; 120 INT stopSlope; 121 INT startRect; 122 INT stopRect; 123 124 INT taperAnaLen; 125 INT taperSynLen; 126 FIXP_WIN pTaperAna__FDK[MAX_TIME_SLOTS]; 127 FIXP_WIN pTaperSyn__FDK[MAX_TIME_SLOTS]; 128 129} FRAMEWINDOW; 130 131typedef enum { 132 FIX_INVALID = -1, 133 FIX_RECT_SMOOTH = 0, 134 FIX_SMOOTH_RECT = 1, 135 FIX_LARGE_SMOOTH = 2, 136 FIX_RECT_TRIANG = 3 137 138} FIX_TYPE; 139 140typedef enum { 141 VAR_INVALID = -1, 142 VAR_HOLD = 0, 143 VAR_ISOLATE = 1 144 145} VAR_TYPE; 146 147/* Constants *****************************************************************/ 148 149/* Function / Class Declarations *********************************************/ 150 151/* Function / Class Definition ***********************************************/ 152static void calcTaperWin(FIXP_WIN *pTaperWin, INT timeSlots) { 153 FIXP_DBL x; 154 int i, scale; 155 156 for (i = 0; i < timeSlots; i++) { 157 x = fDivNormHighPrec((FIXP_DBL)i, (FIXP_DBL)timeSlots, &scale); 158 159 if (scale < 0) { 160 pTaperWin[i] = FX_DBL2FX_WIN(x >> (-scale)); 161 } else { 162 pTaperWin[i] = FX_DBL2FX_WIN(x << (scale)); 163 } 164 } 165 pTaperWin[timeSlots] = FX_DBL2FX_WIN((FIXP_DBL)MAXVAL_DBL); 166} 167 168FDK_SACENC_ERROR fdk_sacenc_frameWindow_Create( 169 HANDLE_FRAMEWINDOW *phFrameWindow) { 170 FDK_SACENC_ERROR error = SACENC_OK; 171 172 if (NULL == phFrameWindow) { 173 error = SACENC_INVALID_HANDLE; 174 } else { 175 /* Memory Allocation */ 176 FDK_ALLOCATE_MEMORY_1D(*phFrameWindow, 1, FRAMEWINDOW); 177 } 178 return error; 179 180bail: 181 fdk_sacenc_frameWindow_Destroy(phFrameWindow); 182 return ((SACENC_OK == error) ? SACENC_MEMORY_ERROR : error); 183} 184 185FDK_SACENC_ERROR fdk_sacenc_frameWindow_Init( 186 HANDLE_FRAMEWINDOW hFrameWindow, 187 const FRAMEWINDOW_CONFIG *const pFrameWindowConfig) { 188 FDK_SACENC_ERROR error = SACENC_OK; 189 190 if ((hFrameWindow == NULL) || (pFrameWindowConfig == NULL)) { 191 error = SACENC_INVALID_HANDLE; 192 } else if (pFrameWindowConfig->nTimeSlotsMax < 0) { 193 error = SACENC_INIT_ERROR; 194 } else { 195 int ts; 196 hFrameWindow->bFrameKeep = pFrameWindowConfig->bFrameKeep; 197 hFrameWindow->nTimeSlotsMax = pFrameWindowConfig->nTimeSlotsMax; 198 199 FIXP_WIN winMaxVal = FX_DBL2FX_WIN((FIXP_DBL)MAXVAL_DBL); 200 int timeSlots = pFrameWindowConfig->nTimeSlotsMax; 201 { 202 hFrameWindow->startSlope = 0; 203 hFrameWindow->stopSlope = ((3 * timeSlots) >> 1) - 1; 204 hFrameWindow->startRect = timeSlots >> 1; 205 hFrameWindow->stopRect = timeSlots; 206 calcTaperWin(hFrameWindow->pTaperSyn__FDK, timeSlots >> 1); 207 hFrameWindow->taperSynLen = timeSlots >> 1; 208 } 209 210 /* Calculate Taper for non-rect. ana. windows */ 211 hFrameWindow->taperAnaLen = 212 hFrameWindow->startRect - hFrameWindow->startSlope; 213 for (ts = 0; ts < hFrameWindow->taperAnaLen; ts++) { 214 { hFrameWindow->pTaperAna__FDK[ts] = winMaxVal; } 215 } 216 } 217 218 return error; 219} 220 221FDK_SACENC_ERROR fdk_sacenc_frameWindow_Destroy( 222 HANDLE_FRAMEWINDOW *phFrameWindow) { 223 FDK_SACENC_ERROR error = SACENC_OK; 224 225 if ((NULL != phFrameWindow) && (NULL != *phFrameWindow)) { 226 FDKfree(*phFrameWindow); 227 *phFrameWindow = NULL; 228 } 229 return error; 230} 231 232static FDK_SACENC_ERROR FrameWinList_Reset(FRAMEWIN_LIST *const pFrameWinList) { 233 FDK_SACENC_ERROR error = SACENC_OK; 234 235 if (NULL == pFrameWinList) { 236 error = SACENC_INVALID_HANDLE; 237 } else { 238 int k = 0; 239 for (k = 0; k < MAX_NUM_PARAMS; k++) { 240 pFrameWinList->dat[k].slot = -1; 241 pFrameWinList->dat[k].hold = FW_INTP; 242 } 243 pFrameWinList->n = 0; 244 } 245 return error; 246} 247 248static FDK_SACENC_ERROR FrameWindowList_Add(FRAMEWIN_LIST *const pFrameWinList, 249 const INT slot, 250 const FW_SLOTTYPE hold) { 251 FDK_SACENC_ERROR error = SACENC_OK; 252 253 if (NULL == pFrameWinList) { 254 error = SACENC_INVALID_HANDLE; 255 } else { 256 if (pFrameWinList->n >= MAX_NUM_PARAMS) { /* Place left in List ?*/ 257 error = SACENC_PARAM_ERROR; 258 } else if (pFrameWinList->n > 0 && 259 pFrameWinList->dat[pFrameWinList->n - 1].slot - slot > 0) { 260 error = SACENC_PARAM_ERROR; 261 } else { 262 pFrameWinList->dat[pFrameWinList->n].slot = slot; 263 pFrameWinList->dat[pFrameWinList->n].hold = hold; 264 pFrameWinList->n++; 265 } 266 } 267 return error; 268} 269 270static FDK_SACENC_ERROR FrameWindowList_Remove( 271 FRAMEWIN_LIST *const pFrameWinList, const INT idx) { 272 FDK_SACENC_ERROR error = SACENC_OK; 273 274 if (NULL == pFrameWinList) { 275 error = SACENC_INVALID_HANDLE; 276 } else { 277 int k = 0; 278 if (idx < 0 || idx >= MAX_NUM_PARAMS) { 279 error = SACENC_PARAM_ERROR; 280 } else if (pFrameWinList->n > 0) { 281 if (idx == MAX_NUM_PARAMS - 1) { 282 pFrameWinList->dat[idx].slot = -1; 283 pFrameWinList->dat[idx].hold = FW_INTP; 284 } else { 285 for (k = idx; k < MAX_NUM_PARAMS - 1; k++) { 286 pFrameWinList->dat[k] = pFrameWinList->dat[k + 1]; 287 } 288 } 289 pFrameWinList->n--; 290 } 291 } 292 return error; 293} 294 295static FDK_SACENC_ERROR FrameWindowList_Limit( 296 FRAMEWIN_LIST *const pFrameWinList, const INT ll /*lower limit*/, 297 const INT ul /*upper limit*/ 298) { 299 FDK_SACENC_ERROR error = SACENC_OK; 300 301 if (NULL == pFrameWinList) { 302 error = SACENC_INVALID_HANDLE; 303 } else { 304 int k = 0; 305 for (k = 0; k < pFrameWinList->n; k++) { 306 if (pFrameWinList->dat[k].slot < ll || pFrameWinList->dat[k].slot > ul) { 307 FrameWindowList_Remove(pFrameWinList, k); 308 --k; 309 } 310 } 311 } 312 return error; 313} 314 315FDK_SACENC_ERROR fdk_sacenc_frameWindow_GetWindow( 316 HANDLE_FRAMEWINDOW hFrameWindow, INT tr_pos[MAX_NUM_PARAMS], 317 const INT timeSlots, FRAMINGINFO *const pFramingInfo, 318 FIXP_WIN *pWindowAna__FDK[MAX_NUM_PARAMS], 319 FRAMEWIN_LIST *const pFrameWinList, const INT avoid_keep) { 320 FDK_SACENC_ERROR error = SACENC_OK; 321 322 if ((hFrameWindow == NULL) || (tr_pos == NULL) || (pFramingInfo == NULL) || 323 (pFrameWinList == NULL) || (pWindowAna__FDK == NULL)) { 324 error = SACENC_INVALID_HANDLE; 325 } else { 326 const VAR_TYPE varType = VAR_HOLD; 327 const int tranL = 4; 328 int winCnt = 0; 329 int w, ps; 330 331 int startSlope = hFrameWindow->startSlope; 332 int stopSlope = hFrameWindow->stopSlope; 333 int startRect = hFrameWindow->startRect; 334 int stopRect = hFrameWindow->stopRect; 335 int taperAnaLen = hFrameWindow->taperAnaLen; 336 337 FIXP_WIN winMaxVal = FX_DBL2FX_WIN((FIXP_DBL)MAXVAL_DBL); 338 FIXP_WIN applyRightWindowGain__FDK[MAX_NUM_PARAMS]; 339 FIXP_WIN *pTaperAna__FDK = hFrameWindow->pTaperAna__FDK; 340 341 /* sanity check */ 342 for (ps = 0; ps < MAX_NUM_PARAMS; ps++) { 343 if (pWindowAna__FDK[ps] == NULL) { 344 error = SACENC_INVALID_HANDLE; 345 goto bail; 346 } 347 } 348 349 if ((timeSlots > hFrameWindow->nTimeSlotsMax) || (timeSlots < 0)) { 350 error = SACENC_INVALID_CONFIG; 351 goto bail; 352 } 353 354 /* Reset */ 355 if (SACENC_OK != (error = FrameWinList_Reset(pFrameWinList))) goto bail; 356 357 FDKmemclear(applyRightWindowGain__FDK, sizeof(applyRightWindowGain__FDK)); 358 359 if (tr_pos[0] > -1) { /* Transients in first (left) half? */ 360 int p_l = tr_pos[0]; 361 winCnt = 0; 362 363 /* Create Parameter Positions */ 364 switch (varType) { 365 case VAR_HOLD: 366 if (SACENC_OK != 367 (error = FrameWindowList_Add(pFrameWinList, p_l - 1, FW_HOLD))) 368 goto bail; 369 if (SACENC_OK != 370 (error = FrameWindowList_Add(pFrameWinList, p_l, FW_INTP))) 371 goto bail; 372 break; 373 case VAR_ISOLATE: 374 if (SACENC_OK != 375 (error = FrameWindowList_Add(pFrameWinList, p_l - 1, FW_HOLD))) 376 goto bail; 377 if (SACENC_OK != 378 (error = FrameWindowList_Add(pFrameWinList, p_l, FW_INTP))) 379 goto bail; 380 if (SACENC_OK != (error = FrameWindowList_Add(pFrameWinList, 381 p_l + tranL, FW_HOLD))) 382 goto bail; 383 if (SACENC_OK != (error = FrameWindowList_Add( 384 pFrameWinList, p_l + tranL + 1, FW_INTP))) 385 goto bail; 386 break; 387 default: 388 error = SACENC_INVALID_CONFIG; 389 break; 390 } 391 392 /* Outside of frame? => Kick Out */ 393 if (SACENC_OK != 394 (error = FrameWindowList_Limit(pFrameWinList, 0, timeSlots - 1))) 395 goto bail; 396 397 /* Add timeSlots as temporary border for window creation */ 398 if (SACENC_OK != 399 (error = FrameWindowList_Add(pFrameWinList, timeSlots - 1, FW_HOLD))) 400 goto bail; 401 402 /* Create Windows */ 403 for (ps = 0; ps < pFrameWinList->n - 1; ps++) { 404 if (FW_HOLD != pFrameWinList->dat[ps].hold) { 405 int const start = pFrameWinList->dat[ps].slot; 406 int const stop = pFrameWinList->dat[ps + 1].slot; 407 408 /* Analysis Window */ 409 FDKmemset_flex(pWindowAna__FDK[winCnt], FX_DBL2FX_WIN((FIXP_DBL)0), 410 start); 411 FDKmemset_flex(&pWindowAna__FDK[winCnt][start], winMaxVal, 412 stop - start + 1); 413 FDKmemset_flex(&pWindowAna__FDK[winCnt][stop + 1], 414 FX_DBL2FX_WIN((FIXP_DBL)0), timeSlots - stop - 1); 415 416 applyRightWindowGain__FDK[winCnt] = 417 pWindowAna__FDK[winCnt][timeSlots - 1]; 418 winCnt++; 419 } 420 } /* ps */ 421 422 /* Pop temporary frame border */ 423 if (SACENC_OK != 424 (error = FrameWindowList_Remove(pFrameWinList, pFrameWinList->n - 1))) 425 goto bail; 426 } else { /* No transient in left half of ana. window */ 427 winCnt = 0; 428 429 /* Add paramter set at end of frame */ 430 if (SACENC_OK != 431 (error = FrameWindowList_Add(pFrameWinList, timeSlots - 1, FW_INTP))) 432 goto bail; 433 /* Analysis Window */ 434 FDKmemset_flex(pWindowAna__FDK[winCnt], FX_DBL2FX_WIN((FIXP_DBL)0), 435 startSlope); 436 FDKmemcpy_flex(&pWindowAna__FDK[winCnt][startSlope], 1, pTaperAna__FDK, 1, 437 taperAnaLen); 438 FDKmemset_flex(&pWindowAna__FDK[winCnt][startRect], winMaxVal, 439 timeSlots - startRect); 440 441 applyRightWindowGain__FDK[winCnt] = winMaxVal; 442 winCnt++; 443 } /* if (tr_pos[0] > -1) */ 444 445 for (w = 0; w < winCnt; w++) { 446 if (applyRightWindowGain__FDK[w] > (FIXP_WIN)0) { 447 if (tr_pos[1] > -1) { /* Transients in second (right) half? */ 448 int p_r = tr_pos[1]; 449 450 /* Analysis Window */ 451 FDKmemset_flex(&pWindowAna__FDK[w][timeSlots], winMaxVal, 452 p_r - timeSlots); 453 FDKmemset_flex(&pWindowAna__FDK[w][p_r], FX_DBL2FX_WIN((FIXP_DBL)0), 454 2 * timeSlots - p_r); 455 456 } else { /* No transient in right half of ana. window */ 457 /* Analysis Window */ 458 FDKmemset_flex(&pWindowAna__FDK[w][timeSlots], winMaxVal, 459 stopRect - timeSlots + 1); 460 FDKmemcpy_flex(&pWindowAna__FDK[w][stopRect], 1, 461 &pTaperAna__FDK[taperAnaLen - 1], -1, taperAnaLen); 462 FDKmemset_flex(&pWindowAna__FDK[w][stopSlope + 1], 463 FX_DBL2FX_WIN((FIXP_DBL)0), 464 2 * timeSlots - stopSlope - 1); 465 466 } /* if (tr_pos[1] > -1) */ 467 468 /* Weight */ 469 if (applyRightWindowGain__FDK[w] < winMaxVal) { 470 int ts; 471 for (ts = 0; ts < timeSlots; ts++) { 472 pWindowAna__FDK[w][timeSlots + ts] = 473 FX_DBL2FX_WIN(fMult(pWindowAna__FDK[w][timeSlots + ts], 474 applyRightWindowGain__FDK[w])); 475 } 476 } 477 } /* if (applyRightWindowGain[w] > 0.0f) */ 478 else { 479 /* All Zero */ 480 FDKmemset_flex(&pWindowAna__FDK[w][timeSlots], 481 FX_DBL2FX_WIN((FIXP_DBL)0), timeSlots); 482 } 483 } /* loop over windows */ 484 485 if (hFrameWindow->bFrameKeep == 1) { 486 FDKmemcpy_flex(&pWindowAna__FDK[0][2 * timeSlots], 1, 487 &pWindowAna__FDK[0][timeSlots], 1, timeSlots); 488 FDKmemcpy_flex(&pWindowAna__FDK[0][timeSlots], 1, pWindowAna__FDK[0], 1, 489 timeSlots); 490 491 if (avoid_keep != 0) { 492 FDKmemset_flex(pWindowAna__FDK[0], FX_DBL2FX_WIN((FIXP_DBL)0), 493 timeSlots); 494 } else { 495 FDKmemset_flex(pWindowAna__FDK[0], winMaxVal, timeSlots); 496 } 497 } /* if (hFrameWindow->bFrameKeep==1) */ 498 499 /* Feed Info to Bitstream Formatter */ 500 pFramingInfo->numParamSets = pFrameWinList->n; 501 pFramingInfo->bsFramingType = 1; /* variable framing */ 502 for (ps = 0; ps < pFramingInfo->numParamSets; ps++) { 503 pFramingInfo->bsParamSlots[ps] = pFrameWinList->dat[ps].slot; 504 } 505 506 /* if there is just one param set at last slot, 507 use fixed framing to save some bits */ 508 if ((pFramingInfo->numParamSets == 1) && 509 (pFramingInfo->bsParamSlots[0] == timeSlots - 1)) { 510 pFramingInfo->bsFramingType = 0; 511 } 512 513 } /* valid handle */ 514 515bail: 516 517 return error; 518} 519 520FDK_SACENC_ERROR fdk_sacenc_analysisWindowing( 521 const INT nTimeSlots, const INT startTimeSlot, 522 FIXP_WIN *pFrameWindowAna__FDK, const FIXP_DPK *const *const ppDataIn__FDK, 523 FIXP_DPK *const *const ppDataOut__FDK, const INT nHybridBands, 524 const INT dim) { 525 FDK_SACENC_ERROR error = SACENC_OK; 526 527 if ((pFrameWindowAna__FDK == NULL) || (ppDataIn__FDK == NULL) || 528 (ppDataOut__FDK == NULL)) { 529 error = SACENC_INVALID_HANDLE; 530 } else { 531 int i, ts; 532 FIXP_WIN maxVal = FX_DBL2FX_WIN((FIXP_DBL)MAXVAL_DBL); 533 534 if (dim == FW_CHANGE_DIM) { 535 for (ts = startTimeSlot; ts < nTimeSlots; ts++) { 536 FIXP_WIN win = pFrameWindowAna__FDK[ts]; 537 if (win == maxVal) { 538 for (i = 0; i < nHybridBands; i++) { 539 ppDataOut__FDK[i][ts].v.re = ppDataIn__FDK[ts][i].v.re; 540 ppDataOut__FDK[i][ts].v.im = ppDataIn__FDK[ts][i].v.im; 541 } 542 } else { 543 for (i = 0; i < nHybridBands; i++) { 544 ppDataOut__FDK[i][ts].v.re = fMult(win, ppDataIn__FDK[ts][i].v.re); 545 ppDataOut__FDK[i][ts].v.im = fMult(win, ppDataIn__FDK[ts][i].v.im); 546 } 547 } 548 } /* ts */ 549 } else { 550 for (ts = startTimeSlot; ts < nTimeSlots; ts++) { 551 FIXP_WIN win = pFrameWindowAna__FDK[ts]; 552 if (win == maxVal) { 553 for (i = 0; i < nHybridBands; i++) { 554 ppDataOut__FDK[ts][i].v.re = ppDataIn__FDK[ts][i].v.re; 555 ppDataOut__FDK[ts][i].v.im = ppDataIn__FDK[ts][i].v.im; 556 } 557 } else { 558 for (i = 0; i < nHybridBands; i++) { 559 ppDataOut__FDK[ts][i].v.re = fMult(win, ppDataIn__FDK[ts][i].v.re); 560 ppDataOut__FDK[ts][i].v.im = fMult(win, ppDataIn__FDK[ts][i].v.im); 561 } 562 } 563 } /* ts */ 564 } 565 } 566 567 return error; 568} 569