Fix OGG/Vorbis stream looping

Previously, a discontinuity occurred when looping an OGG/Vorbis stream,
this happens due to a bug in J-Ogg library. To work around this issue,
we load the logical ogg stream and vorbis stream from scratch based
on the already loaded existing OGG pages instead of using the
buggy setTime(0) call.
experimental
shadowislord 10 years ago
parent 6bdebb937a
commit e1910fdff2
  1. 23
      jme3-jogg/src/main/java/com/jme3/audio/plugins/CachedOggStream.java
  2. 95
      jme3-jogg/src/main/java/com/jme3/audio/plugins/OGGLoader.java

@ -62,6 +62,7 @@ public class CachedOggStream implements PhysicalOggStream {
private OggPage lastPage; private OggPage lastPage;
private int pageNumber; private int pageNumber;
private int serialno;
public CachedOggStream(InputStream in) throws IOException { public CachedOggStream(InputStream in) throws IOException {
sourceStream = in; sourceStream = in;
@ -106,6 +107,19 @@ public class CachedOggStream implements PhysicalOggStream {
} }
} }
public LogicalOggStream reloadLogicalOggStream() {
logicalStreams.clear();
LogicalOggStreamImpl los = new LogicalOggStreamImpl(this, serialno);
logicalStreams.put(serialno, los);
for (IntMap.Entry<OggPage> entry : oggPages) {
los.addPageNumberMapping(entry.getKey());
los.addGranulePosition(entry.getValue().getAbsoluteGranulePosition());
}
return los;
}
private int readOggNextPage() throws IOException { private int readOggNextPage() throws IOException {
if (eos) if (eos)
return -1; return -1;
@ -120,10 +134,11 @@ public class CachedOggStream implements PhysicalOggStream {
} }
LogicalOggStreamImpl los = (LogicalOggStreamImpl) logicalStreams.get(op.getStreamSerialNumber()); LogicalOggStreamImpl los = (LogicalOggStreamImpl) logicalStreams.get(op.getStreamSerialNumber());
if(los == null) { if (los == null) {
los = new LogicalOggStreamImpl(this, op.getStreamSerialNumber()); serialno = op.getStreamSerialNumber();
logicalStreams.put(op.getStreamSerialNumber(), los); los = new LogicalOggStreamImpl(this, op.getStreamSerialNumber());
los.checkFormat(op); logicalStreams.put(op.getStreamSerialNumber(), los);
los.checkFormat(op);
} }
los.addPageNumberMapping(pageNumber); los.addPageNumberMapping(pageNumber);

@ -49,8 +49,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Collection; import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
public class OGGLoader implements AssetLoader { public class OGGLoader implements AssetLoader {
@ -66,9 +64,13 @@ public class OGGLoader implements AssetLoader {
private static class JOggInputStream extends InputStream { private static class JOggInputStream extends InputStream {
private boolean endOfStream = false; private boolean endOfStream = false;
protected final VorbisStream vs; protected PhysicalOggStream ps;
protected LogicalOggStream ls;
protected VorbisStream vs;
public JOggInputStream(VorbisStream vs){ public JOggInputStream(PhysicalOggStream ps, LogicalOggStream ls, VorbisStream vs){
this.ps = ps;
this.ls = ls;
this.vs = vs; this.vs = vs;
} }
@ -83,35 +85,34 @@ public class OGGLoader implements AssetLoader {
} }
@Override @Override
public int read(byte[] buf, int offset, int length) throws IOException{ public int read(byte[] buf, int offset, int length) throws IOException {
if (endOfStream) if (endOfStream) {
return -1; return -1;
}
int bytesRead = 0, cnt = 0; int bytesRead = 0, cnt = 0;
assert length % 2 == 0; // read buffer should be even assert length % 2 == 0; // read buffer should be even
while (bytesRead <length) { while (bytesRead < length) {
if ((cnt = vs.readPcm(buf, offset + bytesRead,length - bytesRead)) <= 0) { if ((cnt = vs.readPcm(buf, offset + bytesRead, length - bytesRead)) <= 0) {
System.out.println("Read "+cnt+" bytes"); System.out.println("Read " + cnt + " bytes");
System.out.println("offset "+offset); System.out.println("offset " + offset);
System.out.println("bytesRead "+bytesRead); System.out.println("bytesRead " + bytesRead);
System.out.println("buf length "+length); System.out.println("buf length " + length);
for (int i = 0; i < bytesRead; i++) { for (int i = 0; i < bytesRead; i++) {
System.out.print(buf[i]); System.out.print(buf[i]);
} }
System.out.println(""); System.out.println("");
System.out.println("EOS"); System.out.println("EOS");
endOfStream = true; endOfStream = true;
break; break;
} }
bytesRead += cnt; bytesRead += cnt;
} }
swapBytes(buf, offset, bytesRead); swapBytes(buf, offset, bytesRead);
return bytesRead; return bytesRead;
} }
@Override @Override
@ -122,30 +123,25 @@ public class OGGLoader implements AssetLoader {
} }
private static class SeekableJOggInputStream extends JOggInputStream implements SeekableStream { private static class SeekableJOggInputStream extends JOggInputStream implements SeekableStream {
private LogicalOggStream los; public SeekableJOggInputStream(PhysicalOggStream ps, LogicalOggStream ls, VorbisStream vs){
private float duration; super(ps, ls, vs);
public SeekableJOggInputStream(VorbisStream vs, LogicalOggStream los, float duration){
super(vs);
this.los = los;
this.duration = duration;
} }
public void setTime(float time) { public void setTime(float time) {
System.out.println("--setTime--)"); if (time != 0.0) {
System.out.println("max granule : "+los.getMaximumGranulePosition()); throw new UnsupportedOperationException("OGG/Vorbis seeking only supported for time = 0");
System.out.println("current granule : "+los.getTime()); }
System.out.println("asked Time : "+time);
System.out.println("new granule : "+(time/duration*los.getMaximumGranulePosition()));
System.out.println("new granule2 : "+(time*vs.getIdentificationHeader().getSampleRate()));
try { try {
los.setTime((long)(time*vs.getIdentificationHeader().getSampleRate())); // HACK: Reload the logical and vorbis streams from scratch
// based on the existing ogg page data.
// This fixes an audio discontinuity issue when looping
// an streaming OGG file via setTime(0).
ls = ((CachedOggStream) ps).reloadLogicalOggStream();
vs = new VorbisStream(ls);
} catch (IOException ex) { } catch (IOException ex) {
Logger.getLogger(OGGLoader.class.getName()).log(Level.SEVERE, null, ex); ex.printStackTrace();
} }
} }
@ -237,11 +233,11 @@ public class OGGLoader implements AssetLoader {
} }
} }
private InputStream readToStream(boolean seekable,float streamDuration){ private InputStream readToStream(boolean seekable) {
if(seekable){ if (seekable) {
return new SeekableJOggInputStream(vorbisStream,loStream,streamDuration); return new SeekableJOggInputStream(oggStream, loStream, vorbisStream);
}else{ } else {
return new JOggInputStream(vorbisStream); return new JOggInputStream(oggStream, loStream, vorbisStream);
} }
} }
@ -275,7 +271,7 @@ public class OGGLoader implements AssetLoader {
// might return -1 if unknown // might return -1 if unknown
float streamDuration = computeStreamDuration(); float streamDuration = computeStreamDuration();
audioStream.updateData(readToStream(oggStream.isSeekable(),streamDuration), streamDuration); audioStream.updateData(readToStream(oggStream.isSeekable()), streamDuration);
return audioStream; return audioStream;
} }
} }
@ -292,12 +288,7 @@ public class OGGLoader implements AssetLoader {
InputStream in = null; InputStream in = null;
try { try {
in = info.openStream(); in = info.openStream();
AudioData data = load(in, readStream, streamCache); return load(in, readStream, streamCache);
if (data instanceof AudioStream){
// audio streams must remain open
in = null;
}
return data;
} finally { } finally {
if (in != null){ if (in != null){
in.close(); in.close();

Loading…
Cancel
Save