diff --git a/src/org/jitsi/impl/neomedia/device/AudioSystem.java b/src/org/jitsi/impl/neomedia/device/AudioSystem.java index 5a8f372839c8270f967b80c8d0bde08091ba1ea5..bc3a9d784a89ce1f0260a04e45d0c3691c4077ae 100644 --- a/src/org/jitsi/impl/neomedia/device/AudioSystem.java +++ b/src/org/jitsi/impl/neomedia/device/AudioSystem.java @@ -421,21 +421,20 @@ public List<CaptureDeviceInfo2> getDevices(DataFlow dataFlow) * @return the FMJ format of the specified <tt>audioInputStream</tt> or * <tt>null</tt> if such an FMJ format could not be determined */ - public Format getFormat(InputStream audioInputStream) + public javax.media.format.AudioFormat getFormat( + InputStream audioInputStream) { if ((audioInputStream instanceof AudioInputStream)) { - AudioFormat audioInputStreamFormat - = ((AudioInputStream) audioInputStream).getFormat(); + AudioFormat af = ((AudioInputStream) audioInputStream).getFormat(); return new javax.media.format.AudioFormat( javax.media.format.AudioFormat.LINEAR, - audioInputStreamFormat.getSampleRate(), - audioInputStreamFormat.getSampleSizeInBits(), - audioInputStreamFormat.getChannels()); + af.getSampleRate(), + af.getSampleSizeInBits(), + af.getChannels()); } - return null; } diff --git a/src/org/jitsi/impl/neomedia/notify/AudioSystemClipImpl.java b/src/org/jitsi/impl/neomedia/notify/AudioSystemClipImpl.java index 63a88d6d6e2721f4d8f8dd43971bcc9df647c589..bafd820cc27092b266d046825634274d4ab4c519 100644 --- a/src/org/jitsi/impl/neomedia/notify/AudioSystemClipImpl.java +++ b/src/org/jitsi/impl/neomedia/notify/AudioSystemClipImpl.java @@ -37,6 +37,13 @@ public class AudioSystemClipImpl private static final Logger logger = Logger.getLogger(AudioSystemClipImpl.class); + /** + * The minimum duration in milliseconds to be assumed for the audio streams + * played by <tt>AudioSystemClipImpl</tt> in order to ensure that they are + * played back long enough to be heard. + */ + private static final long MIN_AUDIO_STREAM_DURATION = 200; + private final AudioSystem audioSystem; private Buffer buffer; @@ -109,7 +116,9 @@ protected void exitRunOnceInPlayThread() } } - @Override + /** + * {@inheritDoc} + */ protected boolean runOnceInPlayThread() { InputStream audioStream = null; @@ -126,10 +135,16 @@ protected boolean runOnceInPlayThread() return false; Codec resampler = null; + boolean success = true; + AudioFormat audioStreamFormat = null; + int audioStreamLength = 0; + long rendererProcessStartTime = 0; try { - Format rendererFormat = audioSystem.getFormat(audioStream); + Format rendererFormat + = audioStreamFormat + = audioSystem.getFormat(audioStream); if (rendererFormat == null) return false; @@ -205,6 +220,8 @@ protected boolean runOnceInPlayThread() && ((bufferLength = audioStream.read(bufferData)) != -1)) { + audioStreamLength += bufferLength; + if (resampler == null) { rendererBuffer.setLength(bufferLength); @@ -218,7 +235,23 @@ protected boolean runOnceInPlayThread() rendererBuffer.setOffset(0); resampler.process(resamplerBuffer, rendererBuffer); } - while ((renderer.process(rendererBuffer) + + int rendererProcess; + + if (rendererProcessStartTime == 0) + rendererProcessStartTime = System.currentTimeMillis(); + do + { + rendererProcess = renderer.process(rendererBuffer); + if (rendererProcess == Renderer.BUFFER_PROCESSED_FAILED) + { + logger.error( + "Failed to render audio stream " + uri); + success = false; + break; + } + } + while ((rendererProcess & Renderer.INPUT_BUFFER_NOT_CONSUMED) == Renderer.INPUT_BUFFER_NOT_CONSUMED); } @@ -226,14 +259,14 @@ protected boolean runOnceInPlayThread() catch (IOException ioex) { logger.error("Failed to read from audio stream " + uri, ioex); - return false; + success = false; } catch (ResourceUnavailableException ruex) { logger.error( "Failed to open " + renderer.getClass().getName(), ruex); - return false; + success = false; } } catch (ResourceUnavailableException ruex) @@ -243,7 +276,7 @@ protected boolean runOnceInPlayThread() logger.error( "Failed to open " + resampler.getClass().getName(), ruex); - return false; + success = false; } } finally @@ -262,7 +295,64 @@ protected boolean runOnceInPlayThread() if (resampler != null) resampler.close(); + + /* + * XXX We do not know whether the Renderer implementation of the + * stop method will wait for the playback to complete. + */ + if (success + && (audioStreamFormat != null) + && (audioStreamLength > 0) + && (rendererProcessStartTime > 0) + && isStarted()) + { + long audioStreamDuration + = (audioStreamFormat.computeDuration(audioStreamLength) + + 999999) + / 1000000; + + if (audioStreamDuration > 0) + { + /* + * XXX The estimation is not accurate because we do not + * know, for example, how much the Renderer may be buffering + * before it starts the playback. + */ + audioStreamDuration += MIN_AUDIO_STREAM_DURATION; + + boolean interrupted = false; + + synchronized (sync) + { + while (isStarted()) + { + long timeout + = System.currentTimeMillis() + - rendererProcessStartTime; + + if ((timeout >= audioStreamDuration) + || (timeout <= 0)) + { + break; + } + else + { + try + { + sync.wait(timeout); + } + catch (InterruptedException ie) + { + interrupted = true; + } + } + } + } + if (interrupted) + Thread.currentThread().interrupt(); + } + } } - return true; + return success; } } diff --git a/src/org/jitsi/service/audionotifier/AbstractSCAudioClip.java b/src/org/jitsi/service/audionotifier/AbstractSCAudioClip.java index d68272dec9d032bef3b0d3a63a3ebd1da36f4136..249d1532ede6e05e6237758f0dfe06fecb0fd620 100644 --- a/src/org/jitsi/service/audionotifier/AbstractSCAudioClip.java +++ b/src/org/jitsi/service/audionotifier/AbstractSCAudioClip.java @@ -65,8 +65,12 @@ public abstract class AbstractSCAudioClip * The <tt>Object</tt> used for internal synchronization purposes which * arise because this instance does the actual playback of audio in a * separate thread. + * <p> + * The synchronization root is exposed to extenders in case they would like + * to, for example, get notified as soon as possible when this instance gets + * stopped. */ - private final Object sync = new Object(); + protected final Object sync = new Object(); /** * The <tt>String</tt> uri of the audio to be played by this instance.