From 7393f7916579cf26b7bbdc060756e37bc2c6d61f Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Wed, 29 Apr 2015 23:56:56 -0400 Subject: [PATCH] AudioSource: add method to get playback time As was requested on the forum, getting playback time / position is needed to perform proper audio / video synchronization. --- .../AndroidMediaPlayerAudioRenderer.java | 5 ++ .../main/java/com/jme3/audio/AudioNode.java | 8 +++ .../java/com/jme3/audio/AudioRenderer.java | 1 + .../main/java/com/jme3/audio/AudioSource.java | 5 ++ .../main/java/com/jme3/audio/AudioStream.java | 13 ++++ .../jme3/audio/openal/ALAudioRenderer.java | 60 +++++++++++++++++++ 6 files changed, 92 insertions(+) diff --git a/jme3-android/src/main/java/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java b/jme3-android/src/main/java/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java index 7ed04658e..394cc257b 100644 --- a/jme3-android/src/main/java/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java +++ b/jme3-android/src/main/java/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java @@ -525,4 +525,9 @@ public class AndroidMediaPlayerAudioRenderer implements AudioRenderer, @Override public void deleteFilter(Filter filter) { } + + @Override + public float getSourcePlaybackTime(AudioSource src) { + throw new UnsupportedOperationException("Not supported yet."); + } } diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java index 0e75db9fe..c4dcfabf0 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java @@ -409,6 +409,14 @@ public class AudioNode extends Node implements AudioSource { play(); } } + + @Override + public float getPlaybackTime() { + if (channel >= 0) + return getRenderer().getSourcePlaybackTime(this); + else + return 0; + } public Vector3f getPosition() { return getWorldTranslation(); diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioRenderer.java b/jme3-core/src/main/java/com/jme3/audio/AudioRenderer.java index 78ea88e91..695999e48 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioRenderer.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioRenderer.java @@ -59,6 +59,7 @@ public interface AudioRenderer { public void updateSourceParam(AudioSource src, AudioParam param); public void updateListenerParam(Listener listener, ListenerParam param); + public float getSourcePlaybackTime(AudioSource src); public void deleteFilter(Filter filter); public void deleteAudioData(AudioData ad); diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioSource.java b/jme3-core/src/main/java/com/jme3/audio/AudioSource.java index 3aa23b78d..75a4e70f9 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioSource.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioSource.java @@ -95,6 +95,11 @@ public interface AudioSource { * @return the time offset in the sound sample when to start playing. */ public float getTimeOffset(); + + /** + * @return the current playback position of the source in seconds. + */ + public float getPlaybackTime(); /** * @return The velocity of the audio source. diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioStream.java b/jme3-core/src/main/java/com/jme3/audio/AudioStream.java index f7ff4c04b..598ae189c 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioStream.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioStream.java @@ -54,6 +54,8 @@ public class AudioStream extends AudioData implements Closeable { protected boolean eof = false; protected int[] ids; + protected int unqueuedBuffersBytes = 0; + public AudioStream() { super(); } @@ -196,10 +198,21 @@ public class AudioStream extends AudioData implements Closeable { return in instanceof SeekableStream; } + public int getUnqueuedBufferBytes() { + return unqueuedBuffersBytes; + } + + public void setUnqueuedBufferBytes(int unqueuedBuffers) { + this.unqueuedBuffersBytes = unqueuedBuffers; + } + public void setTime(float time) { if (in instanceof SeekableStream) { ((SeekableStream) in).setTime(time); eof = false; + + // TODO: when we actually support seeking, this will need to be properly set. + unqueuedBuffersBytes = 0; } else { throw new IllegalStateException( "Cannot use setTime on a stream that " diff --git a/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java b/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java index c3ccea741..62f04018a 100644 --- a/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java +++ b/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java @@ -301,6 +301,58 @@ public class ALAudioRenderer implements AudioRenderer, Runnable { f.clearUpdateNeeded(); } + @Override + public float getSourcePlaybackTime(AudioSource src) { + checkDead(); + synchronized (threadLock) { + if (audioDisabled) { + return 0; + } + + // See comment in updateSourceParam(). + if (src.getChannel() < 0) { + return 0; + } + + int id = channels[src.getChannel()]; + AudioData data = src.getAudioData(); + int playbackOffsetBytes = 0; + + if (data instanceof AudioStream) { + // Because audio streams are processed in buffer chunks, + // we have to compute the amount of time the stream was already + // been playing based on the number of buffers that were processed. + AudioStream stream = (AudioStream) data; + + // NOTE: the assumption is that all enqueued buffers are the same size. + // this is currently enforced by fillBuffer(). + + // The number of unenqueued bytes that the decoder thread + // keeps track of. + int unqueuedBytes = stream.getUnqueuedBufferBytes(); + + // Additional processed buffers that the decoder thread + // did not unenqueue yet (it only updates 20 times per second). + int unqueuedBytesExtra = al.alGetSourcei(id, AL_BUFFERS_PROCESSED) * BUFFER_SIZE; + + // Total additional bytes that need to be considered. + playbackOffsetBytes = unqueuedBytes; // + unqueuedBytesExtra; + } + + // Add byte offset from source (for both streams and buffers) + playbackOffsetBytes += al.alGetSourcei(id, AL_BYTE_OFFSET); + + // Compute time value from bytes + // E.g. for 44100 source with 2 channels and 16 bits per sample: + // (44100 * 2 * 16 / 8) = 176400 + int bytesPerSecond = (data.getSampleRate() * + data.getChannels() * + data.getBitsPerSample() / 8); + + return (float)playbackOffsetBytes / bytesPerSecond; + } + } + public void updateSourceParam(AudioSource src, AudioParam param) { checkDead(); synchronized (threadLock) { @@ -648,6 +700,7 @@ public class ALAudioRenderer implements AudioRenderer, Runnable { private boolean fillStreamingSource(int sourceId, AudioStream stream, boolean looping) { boolean success = false; int processed = al.alGetSourcei(sourceId, AL_BUFFERS_PROCESSED); + int unqueuedBufferBytes = 0; for (int i = 0; i < processed; i++) { int buffer; @@ -656,6 +709,11 @@ public class ALAudioRenderer implements AudioRenderer, Runnable { al.alSourceUnqueueBuffers(sourceId, 1, ib); buffer = ib.get(0); + // XXX: assume that reading from AudioStream always + // gives BUFFER_SIZE amount of bytes! This might not always + // be the case... + unqueuedBufferBytes += BUFFER_SIZE; + boolean active = fillBuffer(stream, buffer); if (!active && !stream.isEOF()) { @@ -682,6 +740,8 @@ public class ALAudioRenderer implements AudioRenderer, Runnable { break; } } + + stream.setUnqueuedBufferBytes(stream.getUnqueuedBufferBytes() + unqueuedBufferBytes); return success; }