001package jmri.jmrit.audio; 002 003import com.jogamp.openal.AL; 004import java.util.Queue; 005import javax.vecmath.Vector3f; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009/** 010 * JOAL implementation of the Audio Source sub-class. 011 * <p> 012 * For now, no system-specific implementations are forseen - this will remain 013 * internal-only 014 * <br><br><hr><br><b> 015 * This software is based on or using the JOAL Library available from 016 * <a href="http://jogamp.org/joal/www/">http://jogamp.org/joal/www/</a> 017 * </b><br><br> 018 * JOAL is released under the BSD license. The full license terms follow: 019 * <br><i> 020 * Copyright (c) 2003-2006 Sun Microsystems, Inc. All Rights Reserved. 021 * <br> 022 * Redistribution and use in source and binary forms, with or without 023 * modification, are permitted provided that the following conditions are 024 * met: 025 * <br> 026 * - Redistribution of source code must retain the above copyright 027 * notice, this list of conditions and the following disclaimer. 028 * <br> 029 * - Redistribution in binary form must reproduce the above copyright 030 * notice, this list of conditions and the following disclaimer in the 031 * documentation and/or other materials provided with the distribution. 032 * <br> 033 * Neither the name of Sun Microsystems, Inc. or the names of 034 * contributors may be used to endorse or promote products derived from 035 * this software without specific prior written permission. 036 * <br> 037 * This software is provided "AS IS," without a warranty of any kind. ALL 038 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, 039 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A 040 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN 041 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR 042 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR 043 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR 044 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR 045 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 046 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, 047 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF 048 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 049 * <br> 050 * You acknowledge that this software is not designed or intended for use 051 * in the design, construction, operation or maintenance of any nuclear 052 * facility. 053 * <br><br><br></i> 054 * <hr> 055 * This file is part of JMRI. 056 * <p> 057 * JMRI is free software; you can redistribute it and/or modify it under the 058 * terms of version 2 of the GNU General Public License as published by the Free 059 * Software Foundation. See the "COPYING" file for a copy of this license. 060 * <p> 061 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 062 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 063 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 064 * 065 * @author Matthew Harris copyright (c) 2009 066 */ 067public class JoalAudioSource extends AbstractAudioSource { 068 069 private static AL al = JoalAudioFactory.getAL(); 070 071 private boolean _initialised = false; 072 073 private int[] _source = new int[1]; 074 075 private int[] _alState = new int[1]; 076 077 /** 078 * Constructor for new JoalAudioSource with system name 079 * 080 * @param systemName AudioSource object system name (e.g. IAS1) 081 */ 082 public JoalAudioSource(String systemName) { 083 super(systemName); 084 log.debug("New JoalAudioSource: {}", systemName); 085 _initialised = init(); 086 } 087 088 /** 089 * Constructor for new JoalAudioSource with system name and user name 090 * 091 * @param systemName AudioSource object system name (e.g. IAS1) 092 * @param userName AudioSource object user name 093 */ 094 public JoalAudioSource(String systemName, String userName) { 095 super(systemName, userName); 096 if (log.isDebugEnabled()) { 097 log.debug("New JoalAudioSource: {} ({})", userName, systemName); 098 } 099 _initialised = init(); 100 } 101 102 /** 103 * Initialise this AudioSource 104 * 105 * @return True if initialised 106 */ 107 private boolean init() { 108 // Check that the JoalAudioFactory exists 109 if (al == null) { 110 log.warn("Al Factory not yet initialised"); 111 return false; 112 } 113 114 // Now, check that the audio command thread exists 115 if (!isAudioAlive()) { 116 log.debug("Command thread not yet alive..."); 117 return false; 118 } else { 119 log.debug("Command thread is alive - continue."); 120 } 121 122 // Generate the AudioSource 123 al.alGenSources(1, _source, 0); 124 if (JoalAudioFactory.checkALError()) { 125 log.warn("Error creating JoalSource ({})", this.getSystemName()); 126 _source = null; 127 return false; 128 } 129 return true; 130 } 131 132 /** 133 * Queue a single AudioBuffer on this source. 134 * 135 * (called from DefaultAudioFactory command queue) 136 * 137 * @param audioBuffer AudioBuffer to queue 138 * @return True if successfully queued. 139 */ 140 @Override 141 public boolean queueAudioBuffer(AudioBuffer audioBuffer) { 142 // First check we've been initialised 143 if (!_initialised || !isAudioAlive()) { 144 log.error("Source Not Initialized: {}", this.getSystemName()); 145 return false; 146 } 147 148 if (audioBuffer instanceof JoalAudioBuffer) { 149 int[] bids = new int[1]; 150 // Make an int[] of the buffer ids 151 bids[0] = ((JoalAudioBuffer) audioBuffer).getDataStorageBuffer()[0]; 152 if (log.isDebugEnabled()) { 153 log.debug("Queueing Buffer: {} bid: {} Source: {}", audioBuffer.getSystemName(), ((JoalAudioBuffer) audioBuffer).getDataStorageBuffer()[0], this.getSystemName()); 154 } 155 156 // Bind this AudioSource to the specified AudioBuffer 157 al.alSourceQueueBuffers(_source[0], 1, bids, 0); 158 if (JoalAudioFactory.checkALError()) { 159 log.warn("Error queueing JoalSource ({}) to AudioBuffers ({})", this.getSystemName(), audioBuffer.getDisplayName()); 160 return false; 161 } 162 163 if (log.isDebugEnabled()) { 164 log.debug("Queue JoalAudioBuffer ({}) to JoalAudioSource ({})", audioBuffer.getSystemName(), this.getSystemName()); 165 } 166 return true; 167 } else { 168 throw new IllegalArgumentException(audioBuffer.getSystemName() + " is not a JoalAudioBuffer"); 169 } 170 } 171 172 /** 173 * Queue a list of AudioBuffers on this source. 174 * 175 * (called from DefaultAudioFactory command queue) 176 * 177 * @param audioBuffers AudioBuffers to queue 178 * @return True if successfully queued. 179 */ 180 @Override 181 public boolean queueAudioBuffers(Queue<AudioBuffer> audioBuffers) { 182 // First check we've been initialised 183 if (!_initialised || !isAudioAlive()) { 184 return false; 185 } 186 187 // Make an int[] of the buffer ids 188 int[] bids = new int[1]; 189 int i = 0; 190 // While the list isn't empty, pop elements and process. 191 AudioBuffer b; 192 while ((b = audioBuffers.poll()) != null) { 193 if (b instanceof JoalAudioBuffer) { 194 bids[0] = ((JoalAudioBuffer) b).getDataStorageBuffer()[0]; 195 } else { 196 throw new IllegalArgumentException(b.getSystemName() + " is not a JoalAudioBuffer"); 197 } 198 al.alSourceQueueBuffers(_source[0], 1, bids, 0); 199 if (log.isDebugEnabled()) { 200 log.debug("Queueing Buffer [{}] {}", i, b.getSystemName()); 201 } 202 i++; 203 if (JoalAudioFactory.checkALError()) { 204 log.warn("Error queueing JoalSource ({}) to AudioBuffers ({}) etc.", this.getSystemName(), b.getDisplayName()); 205 return false; 206 } 207 } 208 209 // Bind this AudioSource to the specified AudioBuffer 210 //al.alSourceQueueBuffers(_source[0], bids.length, bids, 0); 211 //al.alSourcei(_source[0], AL.AL_BUFFER, ((JoalAudioBuffer)audioBuffer).getDataStorageBuffer()[0]); 212 return true; 213 } 214 215 /** 216 * Remove all processed AudioBuffers from this Source. 217 * 218 * @return True if successful. 219 */ 220 @Override 221 public boolean unqueueAudioBuffers() { 222 // First check we've been initialised 223 if (!_initialised || !isAudioAlive()) { 224 return false; 225 } 226 227 int[] num_processed = new int[1]; 228 229 // How many processed buffers are there? 230 al.alGetSourcei(_source[0], AL.AL_BUFFERS_PROCESSED, num_processed, 0); 231 if (JoalAudioFactory.checkALError()) { 232 log.warn("Error getting # processed buffers from JoalSource ({})", this.getSystemName()); 233 return false; 234 } 235 236 // Try to unqueue them all. 237 if (num_processed[0] > 0) { 238 int[] bids = new int[num_processed[0]]; 239 al.alSourceUnqueueBuffers(_source[0], num_processed[0], bids, 0); 240 if (JoalAudioFactory.checkALError()) { 241 log.warn("Error removing {} buffers from JoalSource ({})", num_processed[0], this.getSystemName()); 242 return false; 243 } 244 } 245 if (log.isDebugEnabled()) { 246 log.debug("Removed {} buffers from JoalAudioSource ({})", num_processed[0], this.getSystemName()); 247 } 248 return (numQueuedBuffers() != 0); 249 } 250 251 @Override 252 public int numProcessedBuffers() { 253 if (!isAudioAlive()) { 254 return 0; 255 } 256 257 int[] num_processed = new int[1]; 258 259 // How many processed buffers are there? 260 al.alGetSourcei(_source[0], AL.AL_BUFFERS_PROCESSED, num_processed, 0); 261 if (JoalAudioFactory.checkALError()) { 262 log.warn("Error getting # processed buffers from JoalSource ({})", this.getSystemName()); 263 return 0; 264 } 265 return (num_processed[0]); 266 } 267 268 /** 269 * Report the number of AudioBuffers queued to this source. 270 * 271 * @return number of queued buffers. 272 */ 273 @Override 274 public int numQueuedBuffers() { 275 // First check we've been initialised 276 if (!_initialised || !isAudioAlive()) { 277 return 0; 278 } 279 280 int[] num_queued = new int[1]; 281 // How many queued buffers are there? 282 al.alGetSourcei(_source[0], AL.AL_BUFFERS_QUEUED, num_queued, 0); 283 if (JoalAudioFactory.checkALError()) { 284 log.warn("Error getting # queued buffers from JoalSource ({})", this.getSystemName()); 285 return 0; 286 } 287 288 if (log.isDebugEnabled()) { 289 log.debug("Queued {} buffers on JoalAudioSource ({})", num_queued[0], this.getSystemName()); 290 } 291 return (num_queued[0]); 292 } 293 294 @Override 295 boolean bindAudioBuffer(AudioBuffer audioBuffer) { 296 // First check we've been initialised 297 if (!_initialised || !isAudioAlive()) { 298 return false; 299 } 300 301 // Bind this AudioSource to the specified AudioBuffer 302 al.alSourcei(_source[0], AL.AL_BUFFER, ((JoalAudioBuffer) audioBuffer).getDataStorageBuffer()[0]); 303 if (JoalAudioFactory.checkALError()) { 304 log.warn("Error binding JoalSource ({}) to AudioBuffer ({})", this.getSystemName(), this.getAssignedBufferName()); 305 return false; 306 } 307 308 if (log.isDebugEnabled()) { 309 log.debug("Bind JoalAudioSource ({}) to JoalAudioBuffer ({})", this.getSystemName(), audioBuffer.getSystemName()); 310 } 311 return true; 312 } 313 314 @Override 315 protected void changePosition(Vector3f pos) { 316 if (_initialised && isAudioAlive()) { 317 al.alSource3f(_source[0], AL.AL_POSITION, pos.x, pos.y, pos.z); 318 if (JoalAudioFactory.checkALError()) { 319 log.warn("Error updating position of JoalAudioSource ({})", this.getSystemName()); 320 } 321 } 322 } 323 324 @Override 325 public void setPositionRelative(boolean relative) { 326 super.setPositionRelative(relative); 327 if (_initialised && isAudioAlive()) { 328 al.alSourcei(_source[0], AL.AL_SOURCE_RELATIVE, relative ? AL.AL_TRUE : AL.AL_FALSE); 329 if (JoalAudioFactory.checkALError()) { 330 log.warn("Error updating relative position property of JoalAudioSource ({})", this.getSystemName()); 331 } 332 } 333 } 334 335 @Override 336 public void setVelocity(Vector3f vel) { 337 super.setVelocity(vel); 338 if (_initialised && isAudioAlive()) { 339 al.alSource3f(_source[0], AL.AL_VELOCITY, vel.x, vel.y, vel.z); 340 if (JoalAudioFactory.checkALError()) { 341 log.warn("Error updating velocity of JoalAudioSource ({})", this.getSystemName()); 342 } 343 } 344 } 345 346 @Override 347 public void setGain(float gain) { 348 super.setGain(gain); 349 if (_initialised && isAudioAlive()) { 350 calculateGain(); 351 } 352 } 353 354 @Override 355 public void setPitch(float pitch) { 356 super.setPitch(pitch); 357 if (_initialised && isAudioAlive()) { 358 al.alSourcef(_source[0], AL.AL_PITCH, pitch); 359 if (JoalAudioFactory.checkALError()) { 360 log.warn("Error updating pitch of JoalAudioSource ({})", this.getSystemName()); 361 } 362 } 363 } 364 365 @Override 366 public void setReferenceDistance(float referenceDistance) { 367 super.setReferenceDistance(referenceDistance); 368 if (_initialised && isAudioAlive()) { 369 al.alSourcef(_source[0], AL.AL_REFERENCE_DISTANCE, referenceDistance); 370 if (JoalAudioFactory.checkALError()) { 371 log.warn("Error updating reference distance of JoalAudioSource ({})", this.getSystemName()); 372 } 373 } 374 } 375 376 @Override 377 public void setOffset(long offset) { 378 super.setOffset(offset); 379 if (_initialised && isAudioAlive()) { 380 al.alSourcei(_source[0], AL.AL_SAMPLE_OFFSET, (int) getOffset()); 381 if (JoalAudioFactory.checkALError()) { 382 log.warn("Error updating Sample Offset of JoalAudioSource ({})", this.getSystemName()); 383 } 384 } 385 } 386 387 @Override 388 public void setMaximumDistance(float maximumDistance) { 389 super.setMaximumDistance(maximumDistance); 390 if (_initialised && isAudioAlive()) { 391 al.alSourcef(_source[0], AL.AL_MAX_DISTANCE, maximumDistance); 392 if (JoalAudioFactory.checkALError()) { 393 log.warn("Error updating maximum distance of JoalAudioSource ({})", this.getSystemName()); 394 } 395 } 396 } 397 398 @Override 399 public void setRollOffFactor(float rollOffFactor) { 400 super.setRollOffFactor(rollOffFactor); 401 if (_initialised && isAudioAlive()) { 402 al.alSourcef(_source[0], AL.AL_ROLLOFF_FACTOR, rollOffFactor); 403 if (JoalAudioFactory.checkALError()) { 404 log.warn("Error updating roll-off factor of JoalAudioSource ({})", this.getSystemName()); 405 } 406 } 407 } 408 409 @Override 410 public int getState() { 411 if (isAudioAlive()) { 412 int old = _alState[0]; 413 al.alGetSourcei(_source[0], AL.AL_SOURCE_STATE, _alState, 0); 414 if (_alState[0] != old) { 415 if (_alState[0] == AL.AL_PLAYING) { 416 this.setState(STATE_PLAYING); 417 } else { 418 this.setState(STATE_STOPPED); 419 } 420 } 421 return super.getState(); 422 } else { 423 return STATE_STOPPED; 424 } 425 } 426 427 @Override 428 public void stateChanged(int oldState) { 429 super.stateChanged(oldState); 430 if (_initialised && isAudioAlive()) { 431 al.alSourcef(_source[0], AL.AL_PITCH, this.getPitch()); 432 al.alSourcef(_source[0], AL.AL_GAIN, this.getGain()); 433 al.alSource3f(_source[0], AL.AL_POSITION, this.getCurrentPosition().x, this.getCurrentPosition().y, this.getCurrentPosition().z); 434 al.alSource3f(_source[0], AL.AL_VELOCITY, this.getVelocity().x, this.getVelocity().y, this.getVelocity().z); 435 al.alSourcei(_source[0], AL.AL_LOOPING, this.isLooped() ? AL.AL_TRUE : AL.AL_FALSE); 436 if (JoalAudioFactory.checkALError()) { 437 log.warn("Error updating JoalAudioSource ({})", this.getSystemName()); 438 } 439 } else { 440 _initialised = init(); 441 } 442 } 443 444 @Override 445 protected void doPlay() { 446 log.debug("Play JoalAudioSource ({})", this.getSystemName()); 447 if (_initialised && isAudioAlive() && (isBound() || isQueued())) { 448 doRewind(); 449 doResume(); 450 } 451 } 452 453// @SuppressWarnings("SleepWhileInLoop") 454 @Override 455 protected void doStop() { 456 log.debug("Stop JoalAudioSource ({})", this.getSystemName()); 457 if (_initialised && isAudioAlive() && (isBound() || isQueued())) { 458 al.alSourceStop(_source[0]); 459 doRewind(); 460 } 461 int[] myState = new int[1]; 462 al.alGetSourcei(_source[0], AL.AL_SOURCE_STATE, myState, 0); 463 boolean stopped = myState[0] != AL.AL_LOOPING; 464 while (!stopped) { 465 try { 466 Thread.sleep(5); 467 } catch (InterruptedException ex) { 468 } 469 al.alGetSourcei(_source[0], AL.AL_SOURCE_STATE, myState, 0); 470 stopped = myState[0] != AL.AL_LOOPING; 471 } 472 this.setState(STATE_STOPPED); 473 } 474 475 @Override 476 protected void doPause() { 477 log.debug("Pause JoalAudioSource ({})", this.getSystemName()); 478 if (_initialised && isAudioAlive() && (isBound() || isQueued())) { 479 al.alSourcePause(_source[0]); 480 } 481 this.setState(STATE_STOPPED); 482 } 483 484 @Override 485 protected void doResume() { 486 log.debug("Resume JoalAudioSource ({})", this.getSystemName()); 487 if (_initialised && isAudioAlive() && (isBound() || isQueued())) { 488 calculateGain(); 489 al.alSourcei(_source[0], AL.AL_LOOPING, this.isLooped() ? AL.AL_TRUE : AL.AL_FALSE); 490 al.alSourcePlay(_source[0]); 491 int numLoops = this.getNumLoops(); 492 if (numLoops > 0) { 493 if (log.isDebugEnabled()) { 494 log.debug("Create LoopThread for JoalAudioSource {}", this.getSystemName()); 495 } 496 AudioSourceLoopThread aslt = new AudioSourceLoopThread(this, numLoops); 497 aslt.start(); 498 } 499 } 500 this.setState(STATE_PLAYING); 501 } 502 503 @Override 504 protected void doRewind() { 505 log.debug("Rewind JoalAudioSource ({})", this.getSystemName()); 506 if (_initialised && isAudioAlive() && (isBound() || isQueued())) { 507 al.alSourceRewind(_source[0]); 508 } 509 } 510 511 @Override 512 protected void doFadeIn() { 513 log.debug("Fade-in JoalAudioSource ({})", this.getSystemName()); 514 if (_initialised && isAudioAlive() && (isBound() || isQueued())) { 515 doPlay(); 516 AudioSourceFadeThread asft = new AudioSourceFadeThread(this); 517 asft.start(); 518 } 519 } 520 521 @Override 522 protected void doFadeOut() { 523 log.debug("Fade-out JoalAudioSource ({})", this.getSystemName()); 524 if (_initialised && isAudioAlive() && (isBound() || isQueued())) { 525 AudioSourceFadeThread asft = new AudioSourceFadeThread(this); 526 asft.start(); 527 } 528 } 529 530 @Override 531 protected void cleanup() { 532 log.debug("Cleanup JoalAudioSource ({})", this.getSystemName()); 533 int[] source_type = new int[1]; 534 al.alGetSourcei(_source[0], AL.AL_SOURCE_TYPE, source_type, 0); 535 if (_initialised && (isBound() || isQueued() || source_type[0] == AL.AL_UNDETERMINED) || source_type[0] == AL.AL_STREAMING) { 536 al.alSourceStop(_source[0]); 537 al.alDeleteSources(1, _source, 0); 538 this._source = null; 539 log.debug("...done cleanup"); 540 } 541 } 542 543 @Override 544 protected void calculateGain() { 545 // Adjust gain based on master gain for this source and any 546 // calculated fade gains 547 float currentGain = this.getGain() * this.getFadeGain(); 548 549 // If playing, update the gain 550 if (_initialised && isAudioAlive()) { 551 al.alSourcef(_source[0], AL.AL_GAIN, currentGain); 552 if (JoalAudioFactory.checkALError()) { 553 log.warn("Error updating gain setting of JoalAudioSource ({})", this.getSystemName()); 554 } 555 if (log.isDebugEnabled()) { 556 log.debug("Set current gain of JoalAudioSource {} to {}", this.getSystemName(), currentGain); 557 } 558 } 559 } 560 561 /** 562 * Internal method to return a reference to the OpenAL source buffer 563 * 564 * @return source buffer 565 */ 566 private int[] getSourceBuffer() { 567 return this._source; 568 } 569 570 private static final Logger log = LoggerFactory.getLogger(JoalAudioSource.class); 571 572 /** 573 * An internal class used to create a new thread to monitor looping as, 574 * unlike JavaSound, OpenAL (and, therefore, JOAL) do not provide a 575 * convenient method to loop a sound a specific number of times. 576 */ 577 private static class AudioSourceLoopThread extends AbstractAudioThread { 578 579 /** 580 * Number of times to loop this source 581 */ 582 private int numLoops; 583 584 /** 585 * Reference to the OpenAL source buffer 586 */ 587 private int[] sourceBuffer; 588 589 /** 590 * Constructor that takes handle to looping AudioSource to monitor 591 * 592 * @param audioSource looping AudioSource to monitor 593 * @param numLoops number of loops for this AudioSource to make 594 */ 595 AudioSourceLoopThread(JoalAudioSource audioSource, int numLoops) { 596 super(); 597 this.setName("loopsrc-" + super.getName()); 598 this.sourceBuffer = audioSource.getSourceBuffer(); 599 this.numLoops = numLoops; 600 if (log.isDebugEnabled()) { 601 log.debug("Created AudioSourceLoopThread for AudioSource {} loopcount {}", 602 audioSource.getSystemName(), this.numLoops); 603 } 604 } 605 606 /** 607 * Main processing loop 608 */ 609 @Override 610 public void run() { 611 612 // Current loop count 613 int loopCount = 0; 614 615 // Previous position 616 float oldPos = 0; 617 618 // Current position 619 float[] newPos = new float[1]; 620 621 // Turn on looping 622 JoalAudioSource.al.alSourcei(sourceBuffer[0], AL.AL_LOOPING, AL.AL_TRUE); 623 624 while (!dying()) { 625 626 // Determine current position 627 JoalAudioSource.al.alGetSourcef(sourceBuffer[0], AL.AL_SEC_OFFSET, newPos, 0); 628 629 // Check if it is smaller than the previous position 630 // If so, we've looped so increment the loop counter 631 if (oldPos > newPos[0]) { 632 loopCount++; 633 log.debug("Loop count {}", loopCount); 634 } 635 oldPos = newPos[0]; 636 637 // Check if we've performed sufficient iterations 638 if (loopCount >= numLoops) { 639 die(); 640 } 641 642 // sleep for a while so as not to overload CPU 643 snooze(20); 644 } 645 646 // Turn off looping 647 JoalAudioSource.al.alSourcei(sourceBuffer[0], AL.AL_LOOPING, AL.AL_FALSE); 648 649 // Finish up 650 if (log.isDebugEnabled()) { 651 log.debug("Clean up thread {}", this.getName()); 652 } 653 cleanup(); 654 } 655 656 /** 657 * Shuts this thread down and clears references to created objects 658 */ 659 @Override 660 protected void cleanup() { 661 // Thread is to shutdown 662 die(); 663 664 // Clear references to objects 665 this.sourceBuffer = null; 666 667 // Finalise cleanup in super-class 668 super.cleanup(); 669 } 670 } 671}