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