1 /* 2 * Copyright (C) 2020 Linux Studio Plugins Project <https://lsp-plug.in/> 3 * (C) 2020 Vladimir Sadovnikov <sadko4u@gmail.com> 4 * 5 * This file is part of lsp-plugins 6 * Created on: 6 нояб. 2018 г. 7 * 8 * lsp-plugins is free software: you can redistribute it and/or modify 9 * it under the terms of the GNU Lesser General Public License as published by 10 * the Free Software Foundation, either version 3 of the License, or 11 * any later version. 12 * 13 * lsp-plugins is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU Lesser General Public License for more details. 17 * 18 * You should have received a copy of the GNU Lesser General Public License 19 * along with lsp-plugins. If not, see <https://www.gnu.org/licenses/>. 20 */ 21 22 #include <dsp/endian.h> 23 #include <string.h> 24 #include <stdlib.h> 25 #include <core/debug.h> 26 #include <core/files/lspc/LSPCAudioReader.h> 27 28 #define BUFFER_SIZE 0x2000 29 #define BUFFER_FRAMES 0x400 30 31 namespace lsp 32 { decode_u8(float * vp,const void * src,size_t ns)33 void LSPCAudioReader::decode_u8(float *vp, const void *src, size_t ns) 34 { 35 const uint8_t *p = reinterpret_cast<const uint8_t *>(src); 36 while (ns--) 37 *(vp++) = float(*(p++) - 0x80) / 0x7f; 38 } 39 decode_s8(float * vp,const void * src,size_t ns)40 void LSPCAudioReader::decode_s8(float *vp, const void *src, size_t ns) 41 { 42 const int8_t *p = reinterpret_cast<const int8_t *>(src); 43 while (ns--) 44 *(vp++) = float(*(p++)) / 0x7f; 45 } 46 decode_u16(float * vp,const void * src,size_t ns)47 void LSPCAudioReader::decode_u16(float *vp, const void *src, size_t ns) 48 { 49 const uint16_t *p = reinterpret_cast<const uint16_t *>(src); 50 while (ns--) 51 *(vp++) = float(*(p++) - 0x8000) / 0x7fff; 52 } 53 decode_s16(float * vp,const void * src,size_t ns)54 void LSPCAudioReader::decode_s16(float *vp, const void *src, size_t ns) 55 { 56 const int16_t *p = reinterpret_cast<const int16_t *>(src); 57 while (ns--) 58 *(vp++) = float(*(p++)) / 0x7fff; 59 } 60 decode_u24le(float * vp,const void * src,size_t ns)61 void LSPCAudioReader::decode_u24le(float *vp, const void *src, size_t ns) 62 { 63 const uint8_t *p = reinterpret_cast<const uint8_t *>(src); 64 while (ns--) 65 { 66 int32_t v = 67 __IF_LEBE( 68 p[0] | (p[1] << 8) | (p[2] << 16), 69 p[2] | (p[1] << 8) | (p[0] << 16) 70 ); 71 72 *(vp++) = float(v - 0x800000) / 0x7fffff; 73 p += 3; 74 } 75 } 76 decode_u24be(float * vp,const void * src,size_t ns)77 void LSPCAudioReader::decode_u24be(float *vp, const void *src, size_t ns) 78 { 79 const uint8_t *p = reinterpret_cast<const uint8_t *>(src); 80 while (ns--) 81 { 82 int32_t v = 83 __IF_LEBE( 84 p[2] | (p[1] << 8) | (p[0] << 16), 85 p[0] | (p[1] << 8) | (p[2] << 16) 86 ); 87 88 *(vp++) = float(v - 0x800000) / 0x7fffff; 89 p += 3; 90 } 91 } 92 decode_s24le(float * vp,const void * src,size_t ns)93 void LSPCAudioReader::decode_s24le(float *vp, const void *src, size_t ns) 94 { 95 const uint8_t *p = reinterpret_cast<const uint8_t *>(src); 96 while (ns--) 97 { 98 int32_t v = 99 __IF_LEBE( 100 p[0] | (p[1] << 8) | (p[2] << 16), 101 p[2] | (p[1] << 8) | (p[0] << 16) 102 ); 103 v = (v << 8) >> 8; // Sign-extend value 104 *(vp++) = float(v) / 0x7fffff; 105 p += 3; 106 } 107 } 108 decode_s24be(float * vp,const void * src,size_t ns)109 void LSPCAudioReader::decode_s24be(float *vp, const void *src, size_t ns) 110 { 111 const uint8_t *p = reinterpret_cast<const uint8_t *>(src); 112 while (ns--) 113 { 114 int32_t v = 115 __IF_LEBE( 116 p[2] | (p[1] << 8) | (p[0] << 16), 117 p[0] | (p[1] << 8) | (p[2] << 16) 118 ); 119 v = (v << 8) >> 8; // Sign-extend value 120 *(vp++) = float(v) / 0x7fffff; 121 p += 3; 122 } 123 } 124 decode_u32(float * vp,const void * src,size_t ns)125 void LSPCAudioReader::decode_u32(float *vp, const void *src, size_t ns) 126 { 127 const int32_t *p = reinterpret_cast<const int32_t *>(src); 128 while (ns--) 129 { 130 int32_t v = *(p++) - 0x80000000; 131 *(vp++) = double(v) / 0x7fffffff; 132 } 133 } 134 decode_s32(float * vp,const void * src,size_t ns)135 void LSPCAudioReader::decode_s32(float *vp, const void *src, size_t ns) 136 { 137 const int32_t *p = reinterpret_cast<const int32_t *>(src); 138 while (ns--) 139 { 140 int32_t v = *(p++); 141 *(vp++) = double(v) / 0x7fffffff; 142 } 143 } 144 decode_f32(float * vp,const void * src,size_t ns)145 void LSPCAudioReader::decode_f32(float *vp, const void *src, size_t ns) 146 { 147 const float *p = reinterpret_cast<const float *>(src); 148 while (ns--) 149 *(vp++) = *(p++); 150 } 151 decode_f64(float * vp,const void * src,size_t ns)152 void LSPCAudioReader::decode_f64(float *vp, const void *src, size_t ns) 153 { 154 const double *p = reinterpret_cast<const double *>(src); 155 while (ns--) 156 *(vp++) = *(p++); 157 } 158 LSPCAudioReader()159 LSPCAudioReader::LSPCAudioReader() 160 { 161 sParams.channels = 0; 162 sParams.sample_format = 0; 163 sParams.sample_rate = 0; 164 sParams.codec = 0; 165 sParams.frames = 0; 166 167 pFD = NULL; 168 pRD = NULL; 169 nFlags = 0; 170 nBPS = 0; 171 nFrameSize = 0; 172 nBytesLeft = 0; 173 sBuf.vData = NULL; 174 sBuf.nOff = 0; 175 sBuf.nSize = 0; 176 pDecode = NULL; 177 pFBuffer = NULL; 178 } 179 ~LSPCAudioReader()180 LSPCAudioReader::~LSPCAudioReader() 181 { 182 close(); 183 } 184 close()185 status_t LSPCAudioReader::close() 186 { 187 // Check open status first 188 if (!(nFlags & F_OPENED)) 189 return STATUS_CLOSED; 190 191 // Close reader (if required) 192 status_t res = STATUS_OK; 193 if (pRD != NULL) 194 { 195 status_t xr = (nFlags & F_CLOSE_READER) ? pRD->close() : STATUS_OK; 196 if (nFlags & F_DROP_READER) 197 delete pRD; 198 pRD = NULL; 199 if (res == STATUS_OK) 200 res = xr; 201 } 202 203 // Close LSPC file (if required) 204 if ((nFlags & F_CLOSE_FILE) && (pFD != NULL)) 205 { 206 status_t xr = pFD->close(); 207 pFD = NULL; 208 if (res == STATUS_OK) 209 res = xr; 210 } 211 212 // Drop buffers 213 if (sBuf.vData != NULL) 214 { 215 delete [] sBuf.vData; 216 sBuf.vData = NULL; 217 } 218 219 // Drop buffers 220 if (pFBuffer != NULL) 221 { 222 delete [] pFBuffer; 223 pFBuffer = NULL; 224 } 225 226 nFlags = 0; 227 nBPS = 0; 228 nFrameSize = 0; 229 nBytesLeft = 0; 230 sBuf.nOff = 0; 231 sBuf.nSize = 0; 232 pDecode = NULL; 233 return res; 234 } 235 read_audio_header(LSPCChunkReader * rd)236 status_t LSPCAudioReader::read_audio_header(LSPCChunkReader *rd) 237 { 238 lspc_chunk_audio_header_t hdr; 239 lspc_audio_parameters_t p; 240 241 // Read audio header from chunk 242 ssize_t res = rd->read_header(&hdr, sizeof(lspc_chunk_audio_header_t)); 243 if (res < 0) 244 return status_t(-res); 245 246 // Check version and decode header 247 if (hdr.common.version < 1) 248 return STATUS_CORRUPTED_FILE; 249 if (hdr.common.size < sizeof(lspc_chunk_audio_header_t)) 250 return STATUS_CORRUPTED_FILE; 251 252 p.channels = BE_TO_CPU(hdr.channels); 253 p.sample_format = BE_TO_CPU(hdr.sample_format); 254 p.sample_rate = BE_TO_CPU(hdr.sample_rate); 255 p.codec = BE_TO_CPU(hdr.codec); 256 p.frames = BE_TO_CPU(hdr.frames); 257 258 return apply_params(&p); 259 } 260 apply_params(const lspc_audio_parameters_t * p)261 status_t LSPCAudioReader::apply_params(const lspc_audio_parameters_t *p) 262 { 263 if (p->channels <= 0) 264 return STATUS_BAD_FORMAT; 265 if (p->sample_rate == 0) 266 return STATUS_BAD_FORMAT; 267 if (p->codec != LSPC_CODEC_PCM) 268 return STATUS_UNSUPPORTED_FORMAT; 269 270 // Check sample format support 271 size_t sb = 0; 272 decode_func_t df = NULL; 273 bool le = false; 274 bool arch_le = __IF_LEBE(true, false); 275 276 switch (p->sample_format) 277 { 278 case LSPC_SAMPLE_FMT_U8LE: 279 case LSPC_SAMPLE_FMT_U8BE: 280 df = decode_u8; 281 sb = 1; 282 le = p->sample_format == LSPC_SAMPLE_FMT_U8LE; 283 break; 284 285 case LSPC_SAMPLE_FMT_S8LE: 286 case LSPC_SAMPLE_FMT_S8BE: 287 df = decode_s8; 288 sb = 1; 289 le = p->sample_format == LSPC_SAMPLE_FMT_S8LE; 290 break; 291 292 case LSPC_SAMPLE_FMT_U16LE: 293 case LSPC_SAMPLE_FMT_U16BE: 294 df = decode_u16; 295 sb = 2; 296 le = p->sample_format == LSPC_SAMPLE_FMT_U16LE; 297 break; 298 299 case LSPC_SAMPLE_FMT_S16LE: 300 case LSPC_SAMPLE_FMT_S16BE: 301 df = decode_s16; 302 sb = 2; 303 le = p->sample_format == LSPC_SAMPLE_FMT_S16LE; 304 break; 305 306 case LSPC_SAMPLE_FMT_U24LE: 307 df = decode_u24le; 308 sb = 3; 309 le = true; 310 break; 311 case LSPC_SAMPLE_FMT_U24BE: 312 df = decode_u24be; 313 sb = 3; 314 le = false; 315 break; 316 case LSPC_SAMPLE_FMT_S24LE: 317 df = decode_s24le; 318 sb = 3; 319 le = true; 320 break; 321 case LSPC_SAMPLE_FMT_S24BE: 322 df = decode_s24be; 323 sb = 3; 324 le = false; 325 break; 326 327 case LSPC_SAMPLE_FMT_U32LE: 328 case LSPC_SAMPLE_FMT_U32BE: 329 df = decode_u32; 330 sb = 4; 331 le = p->sample_format == LSPC_SAMPLE_FMT_U32LE; 332 break; 333 334 case LSPC_SAMPLE_FMT_S32LE: 335 case LSPC_SAMPLE_FMT_S32BE: 336 df = decode_s32; 337 sb = 4; 338 le = p->sample_format == LSPC_SAMPLE_FMT_S32LE; 339 break; 340 341 case LSPC_SAMPLE_FMT_F32LE: 342 case LSPC_SAMPLE_FMT_F32BE: 343 df = decode_f32; 344 sb = 4; 345 le = p->sample_format == LSPC_SAMPLE_FMT_F32LE; 346 break; 347 348 case LSPC_SAMPLE_FMT_F64LE: 349 case LSPC_SAMPLE_FMT_F64BE: 350 df = decode_f64; 351 sb = 8; 352 le = p->sample_format == LSPC_SAMPLE_FMT_F64LE; 353 break; 354 355 default: 356 return STATUS_UNSUPPORTED_FORMAT; 357 } 358 359 // Estimate number of bytes to read 360 size_t fz = sb * p->channels; 361 size_t bytes_left = fz * p->frames; 362 363 // Allocate buffers 364 sBuf.vData = new uint8_t[BUFFER_SIZE]; 365 if (sBuf.vData == NULL) 366 return STATUS_NO_MEM; 367 368 pFBuffer = new float[p->channels * BUFFER_FRAMES]; 369 if (pFBuffer == NULL) 370 { 371 delete [] sBuf.vData; 372 sBuf.vData = NULL; 373 return STATUS_NO_MEM; 374 } 375 376 if (le != arch_le) 377 nFlags |= F_REV_BYTES; // Set-up byte-reversal flag 378 379 sParams = *p; 380 nBPS = sb; 381 nFrameSize = fz; 382 nBytesLeft = bytes_left; 383 sBuf.nOff = 0; 384 sBuf.nSize = 0; 385 pDecode = df; 386 387 return STATUS_OK; 388 } 389 open(LSPCFile * lspc,bool auto_close)390 status_t LSPCAudioReader::open(LSPCFile *lspc, bool auto_close) 391 { 392 if (nFlags & F_OPENED) 393 return STATUS_OPENED; 394 nFlags = 0; 395 396 LSPCChunkReader *rd = lspc->find_chunk(LSPC_CHUNK_AUDIO, NULL, 0); 397 if (rd == NULL) 398 return STATUS_NOT_FOUND; 399 400 status_t res = read_audio_header(rd); 401 if (res != STATUS_OK) 402 { 403 rd->close(); 404 return res; 405 } 406 407 pFD = lspc; 408 pRD = rd; 409 nFlags |= F_OPENED | F_CLOSE_READER | F_DROP_READER; 410 if (auto_close) 411 nFlags |= F_CLOSE_FILE; 412 return STATUS_OK; 413 } 414 open(LSPCFile * lspc,uint32_t uid,bool auto_close)415 status_t LSPCAudioReader::open(LSPCFile *lspc, uint32_t uid, bool auto_close) 416 { 417 if (nFlags & F_OPENED) 418 return STATUS_OPENED; 419 nFlags = 0; 420 421 LSPCChunkReader *rd = lspc->read_chunk(uid); 422 if (rd == NULL) 423 return STATUS_NOT_FOUND; 424 else if (rd->magic() != LSPC_CHUNK_AUDIO) 425 { 426 rd->close(); 427 return STATUS_BAD_TYPE; 428 } 429 430 status_t res = read_audio_header(rd); 431 if (res != STATUS_OK) 432 { 433 rd->close(); 434 return res; 435 } 436 437 pFD = lspc; 438 pRD = rd; 439 nFlags |= F_OPENED | F_CLOSE_READER | F_DROP_READER; 440 if (auto_close) 441 nFlags |= F_CLOSE_FILE; 442 return STATUS_OK; 443 } 444 open_raw_magic(LSPCFile * lspc,const lspc_audio_parameters_t * params,uint32_t magic,bool auto_close)445 status_t LSPCAudioReader::open_raw_magic(LSPCFile *lspc, const lspc_audio_parameters_t *params, uint32_t magic, bool auto_close) 446 { 447 if (nFlags & F_OPENED) 448 return STATUS_OPENED; 449 else if (params == NULL) 450 return STATUS_BAD_ARGUMENTS; 451 nFlags = 0; 452 453 LSPCChunkReader *rd = lspc->find_chunk(magic, NULL, 0); 454 if (rd == NULL) 455 return STATUS_NOT_FOUND; 456 457 status_t res = apply_params(params); 458 if (res != STATUS_OK) 459 { 460 rd->close(); 461 return res; 462 } 463 464 pFD = lspc; 465 pRD = rd; 466 nFlags |= F_OPENED | F_CLOSE_READER | F_DROP_READER; 467 if (auto_close) 468 nFlags |= F_CLOSE_FILE; 469 return STATUS_OK; 470 } 471 open_raw_uid(LSPCFile * lspc,const lspc_audio_parameters_t * params,uint32_t uid,bool auto_close)472 status_t LSPCAudioReader::open_raw_uid(LSPCFile *lspc, const lspc_audio_parameters_t *params, uint32_t uid, bool auto_close) 473 { 474 if (nFlags & F_OPENED) 475 return STATUS_OPENED; 476 else if (params == NULL) 477 return STATUS_BAD_ARGUMENTS; 478 nFlags = 0; 479 480 LSPCChunkReader *rd = lspc->read_chunk(uid); 481 if (rd == NULL) 482 return STATUS_NOT_FOUND; 483 484 status_t res = apply_params(params); 485 if (res != STATUS_OK) 486 { 487 rd->close(); 488 return res; 489 } 490 491 pFD = lspc; 492 pRD = rd; 493 nFlags |= F_OPENED | F_CLOSE_READER | F_DROP_READER; 494 if (auto_close) 495 nFlags |= F_CLOSE_FILE; 496 return STATUS_OK; 497 } 498 open_raw(LSPCChunkReader * rd,const lspc_audio_parameters_t * params,bool auto_close)499 status_t LSPCAudioReader::open_raw(LSPCChunkReader *rd, const lspc_audio_parameters_t *params, bool auto_close) 500 { 501 if (nFlags & F_OPENED) 502 return STATUS_OPENED; 503 else if (params == NULL) 504 return STATUS_BAD_ARGUMENTS; 505 nFlags = 0; 506 507 status_t res = apply_params(params); 508 if (res != STATUS_OK) 509 return res; 510 511 pFD = NULL; 512 pRD = rd; 513 nFlags |= F_OPENED; 514 if (auto_close) 515 nFlags |= F_CLOSE_READER; 516 return STATUS_OK; 517 } 518 get_parameters(lspc_audio_parameters_t * dst) const519 status_t LSPCAudioReader::get_parameters(lspc_audio_parameters_t *dst) const 520 { 521 if (!(nFlags & F_OPENED)) 522 return STATUS_CLOSED; 523 else if (dst == NULL) 524 return STATUS_BAD_ARGUMENTS; 525 *dst = sParams; 526 527 return STATUS_OK; 528 } 529 fill_buffer()530 status_t LSPCAudioReader::fill_buffer() 531 { 532 // Move buffer data from end to the beginning 533 size_t bsize = sBuf.nSize - sBuf.nOff; 534 if ((sBuf.nSize > 0) && (bsize > 0)) 535 { 536 ::memmove(sBuf.vData, &sBuf.vData[sBuf.nOff], bsize); 537 sBuf.nSize = bsize; 538 } 539 else 540 sBuf.nSize = 0; 541 sBuf.nOff = 0; 542 543 // Try to read additional data 544 bsize = BUFFER_SIZE - bsize; 545 ssize_t n = pRD->read(&sBuf.vData[sBuf.nSize], bsize); 546 if (n < 0) 547 return status_t(-n); 548 else if (n == 0) // Number of bytes should be multiple of nFrameSize 549 { 550 size_t delta = sBuf.nSize - sBuf.nOff; 551 if (delta >= nFrameSize) 552 return STATUS_OK; 553 else if (delta == 0) 554 return STATUS_EOF; 555 else 556 return STATUS_CORRUPTED_FILE; 557 } 558 559 sBuf.nSize += n; 560 return STATUS_OK; 561 } 562 read_samples(float ** data,size_t frames)563 ssize_t LSPCAudioReader::read_samples(float **data, size_t frames) 564 { 565 if (!(nFlags & F_OPENED)) 566 return STATUS_CLOSED; 567 568 size_t nc = sParams.channels; 569 float **vp = reinterpret_cast<float **>(alloca(nc * sizeof(float *))); 570 for (size_t i=0; i<nc; ++i) 571 vp[i] = data[i]; 572 573 size_t n_read = 0; 574 575 while (n_read < frames) 576 { 577 // Estimate number of frames to read 578 size_t to_read = frames - n_read; 579 if (to_read > BUFFER_FRAMES) 580 to_read = BUFFER_FRAMES; 581 582 // Read frames to temporary buffer 583 ssize_t n = read_frames(pFBuffer, to_read); 584 if (n <= 0) 585 return (n_read > 0) ? n_read : n; 586 587 n_read += n; 588 589 // Unpack frames 590 float *p = pFBuffer; 591 while (n--) 592 { 593 for (size_t j=0; j<nc; ++j, ++p) 594 { 595 if (!vp[j]) 596 continue; 597 *(vp[j]++) = *p; 598 } 599 } 600 } 601 602 return n_read; 603 } 604 read_frames(float * data,size_t frames)605 ssize_t LSPCAudioReader::read_frames(float *data, size_t frames) 606 { 607 if (!(nFlags & F_OPENED)) 608 return STATUS_CLOSED; 609 610 size_t n_read = 0; 611 while (n_read < frames) 612 { 613 size_t to_read = frames - n_read; 614 615 // Ensure that we have enough bytes to read at least one frame 616 size_t avail = sBuf.nSize - sBuf.nOff; 617 if (avail < nFrameSize) 618 { 619 // Try to fill buffer with new data 620 status_t st = fill_buffer(); 621 if (st != STATUS_OK) 622 return (n_read > 0) ? n_read : -st; 623 avail = sBuf.nSize - sBuf.nOff; 624 if (avail < nFrameSize) 625 return (n_read > 0) ? n_read : STATUS_CORRUPTED_FILE; 626 } 627 628 // Perform decode 629 avail /= nFrameSize; 630 if (avail > to_read) 631 avail = to_read; 632 size_t floats = avail * sParams.channels; 633 if (nFlags & F_REV_BYTES) 634 { 635 switch (nBPS) 636 { 637 case 1: 638 case 3: 639 break; 640 case 2: 641 byte_swap(reinterpret_cast<uint16_t *>(&sBuf.vData[sBuf.nOff]), floats); 642 break; 643 case 4: 644 byte_swap(reinterpret_cast<uint32_t *>(&sBuf.vData[sBuf.nOff]), floats); 645 break; 646 case 8: 647 byte_swap(reinterpret_cast<uint64_t *>(&sBuf.vData[sBuf.nOff]), floats); 648 break; 649 default: 650 return STATUS_BAD_STATE; 651 } 652 } 653 654 // Perform decode 655 pDecode(data, &sBuf.vData[sBuf.nOff], floats); 656 657 // Update pointers 658 n_read += avail; 659 sBuf.nOff += avail * nFrameSize; 660 data += floats; 661 } 662 663 return n_read; 664 } 665 skip_frames(size_t frames)666 ssize_t LSPCAudioReader::skip_frames(size_t frames) 667 { 668 if (!(nFlags & F_OPENED)) 669 return STATUS_CLOSED; 670 671 size_t n_skip = 0; 672 while (n_skip < frames) 673 { 674 size_t to_skip = frames - n_skip; 675 676 // Ensure that we have enough bytes to read at least one frame 677 size_t avail = sBuf.nSize - sBuf.nOff; 678 if (avail < nFrameSize) 679 { 680 // Try to fill buffer with new data 681 status_t st = fill_buffer(); 682 if (st != STATUS_OK) 683 return (n_skip > 0) ? n_skip : -st; 684 avail = sBuf.nSize - sBuf.nOff; 685 if (avail < nFrameSize) 686 return (n_skip > 0) ? n_skip : STATUS_CORRUPTED_FILE; 687 } 688 689 // Perform decode 690 avail /= nFrameSize; 691 if (avail > to_skip) 692 avail = to_skip; 693 694 // Update pointers 695 n_skip += avail; 696 sBuf.nOff += avail * nFrameSize; 697 } 698 699 return n_skip; 700 } 701 unique_id() const702 uint32_t LSPCAudioReader::unique_id() const 703 { 704 if (!(nFlags & F_OPENED)) 705 return 0; 706 else if (pRD == NULL) 707 return 0; 708 709 return pRD->unique_id(); 710 } 711 magic() const712 uint32_t LSPCAudioReader::magic() const 713 { 714 if (!(nFlags & F_OPENED)) 715 return 0; 716 else if (pRD == NULL) 717 return 0; 718 719 return pRD->magic(); 720 } 721 722 } /* namespace lsp */ 723