All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.bloodshura.ignitium.assets.ext.sound.internal.OggInputStream Maven / Gradle / Ivy

Go to download

Extensions for the Ignitium Assets library, including new image and sound codecs.

There is a newer version: 1.0.1
Show newest version
package com.github.bloodshura.ignitium.assets.ext.sound.internal;

import com.github.bloodshura.ignitium.memory.Bufferer;
import com.jcraft.jogg.Packet;
import com.jcraft.jogg.Page;
import com.jcraft.jogg.StreamState;
import com.jcraft.jogg.SyncState;
import com.jcraft.jorbis.Block;
import com.jcraft.jorbis.Comment;
import com.jcraft.jorbis.DspState;
import com.jcraft.jorbis.Info;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class OggInputStream extends InputStream {
	boolean endOfBitStream;
	boolean inited = false;
	private byte[] buffer;
	private int bytes;
	private final Comment comment;
	private final byte[] convbuffer;
	private int convsize;
	private final DspState dspState;
	private boolean endOfStream;
	private final InputStream input;
	private final Info oggInfo;
	private final Packet packet;
	private final Page page;
	private final ByteBuffer pcmBuffer;
	private int readIndex;
	private final StreamState streamState;
	private final SyncState syncState;
	private final Block vorbisBlock;

	public OggInputStream(InputStream input) throws IOException {
		this.bytes = 0;
		this.comment = new Comment();
		this.convbuffer = new byte[Bufferer.COMMON_SIZE * 4];
		this.convsize = convbuffer.length;
		this.dspState = new DspState();
		this.endOfBitStream = true;
		this.input = input;
		this.oggInfo = new Info();
		this.packet = new Packet();
		this.page = new Page();
		this.pcmBuffer = Bufferer.newByteBuffer(Bufferer.COMMON_SIZE * 64); // 256 KB
		this.streamState = new StreamState();
		this.syncState = new SyncState();
		this.vorbisBlock = new Block(dspState);

		initVorbis();
		readPCM();
	}

	public boolean atEnd() {
		return endOfStream && readIndex >= pcmBuffer.position();
	}

	@Override
	public int available() {
		return endOfStream ? 0 : 1;
	}

	@Override
	public void close() {
	}

	public int getChannels() {
		return oggInfo.channels;
	}

	public int getSampleRate() {
		return oggInfo.rate;
	}

	@Override
	public int read() {
		if (readIndex >= pcmBuffer.position()) {
			pcmBuffer.clear();

			try {
				readPCM();
			} catch (IOException ignored) {
			}

			this.readIndex = 0;
		}

		if (readIndex >= pcmBuffer.position()) {
			return -1;
		}

		int value = pcmBuffer.get(readIndex);

		if (value < 0) {
			value = 256 + value;
		}

		readIndex++;

		return value;
	}

	@Override
	public int read(byte[] b) {
		return read(b, 0, b.length);
	}

	@Override
	public int read(byte[] b, int off, int len) {
		for (int i = 0; i < len; i++) {
			int value = read();

			if (value >= 0) {
				b[i] = (byte) value;
			} else {
				if (i == 0) {
					return -1;
				}

				return i;
			}
		}

		return len;
	}

	private boolean getPageAndPacket() throws IOException {
		int index = syncState.buffer(Bufferer.COMMON_SIZE);

		if (index == -1) {
			return false;
		}

		this.buffer = syncState.data;

		if (buffer == null) {
			this.endOfStream = true;

			return false;
		}

		try {
			this.bytes = input.read(buffer, index, Bufferer.COMMON_SIZE);
		} catch (IOException exception) {
			throw new IOException("Failure reading Vorbis.");
		}

		syncState.wrote(bytes);

		if (syncState.pageout(page) != 1) {
			if (bytes < Bufferer.COMMON_SIZE) {
				return false;
			}

			throw new IOException("Input does not appear to be an Ogg bitstream.");
		}

		streamState.init(page.serialno());
		oggInfo.init();
		comment.init();

		if (streamState.pagein(page) < 0) {
			throw new IOException("Error reading first page of Ogg bitstream.");
		}

		if (streamState.packetout(packet) != 1) {
			throw new IOException("Error reading initial header packet.");
		}

		if (oggInfo.synthesis_headerin(comment, packet) < 0) {
			throw new IOException("Ogg bitstream does not contain Vorbis audio data.");
		}

		int i = 0;

		while (i < 2) {
			while (i < 2) {
				int result = syncState.pageout(page);

				if (result == 0) {
					break;
				}

				if (result == 1) {
					streamState.pagein(page);

					while (i < 2) {
						result = streamState.packetout(packet);

						if (result == 0) {
							break;
						}

						if (result == -1) {
							throw new IOException("Corrupt secondary header.");
						}

						oggInfo.synthesis_headerin(comment, packet);
						i++;
					}
				}
			}

			index = syncState.buffer(Bufferer.COMMON_SIZE);

			if (index == -1) {
				return false;
			}

			this.buffer = syncState.data;

			try {
				this.bytes = input.read(buffer, index, Bufferer.COMMON_SIZE);
			} catch (IOException exception) {
				throw new IOException("Failed to read Vorbis.");
			}

			if (bytes == 0 && i < 2) {
				throw new IOException("End of file before finding all Vorbis headers.");
			}

			syncState.wrote(bytes);
		}

		convsize = Bufferer.COMMON_SIZE / oggInfo.channels;
		dspState.synthesis_init(oggInfo);
		vorbisBlock.init(dspState);

		return true;
	}

	private void initVorbis() {
		syncState.init();
	}

	private void readPCM() throws IOException {
		boolean bigEndian = ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN);
		boolean wrote = false;

		while (true) {
			if (endOfBitStream) {
				if (!getPageAndPacket()) {
					break;
				}

				this.endOfBitStream = false;
			}

			if (!inited) {
				inited = true;

				return;
			}

			float[][][] _pcm = new float[1][][];
			int[] _index = new int[oggInfo.channels];

			while (!endOfBitStream) {
				while (!endOfBitStream) {
					int result = syncState.pageout(page);

					if (result == 0) {
						break;
					}

					if (result > 0) {
						streamState.pagein(page);

						while (true) {
							result = streamState.packetout(packet);

							if (result == 0) {
								break;
							}

							if (result != -1) {
								int samples;

								if (vorbisBlock.synthesis(packet) == 0) {
									dspState.synthesis_blockin(vorbisBlock);
								}

								while ((samples = dspState.synthesis_pcmout(_pcm, _index)) > 0) {
									float[][] pcm = _pcm[0];
									int bout = samples < convsize ? samples : convsize;

									for (int i = 0; i < oggInfo.channels; i++) {
										int ptr = i * 2;
										int mono = _index[i];

										for (int j = 0; j < bout; j++) {
											int val = (int) (pcm[i][mono + j] * 32767.);

											if (val > 32767) {
												val = 32767;
											} else if (val < -32768) {
												val = -32768;
											} else if (val < 0) {
												val = val | 0x8000;
											}

											if (bigEndian) {
												convbuffer[ptr] = (byte) (val >>> 8);
												convbuffer[ptr + 1] = (byte) val;
											} else {
												convbuffer[ptr] = (byte) val;
												convbuffer[ptr + 1] = (byte) (val >>> 8);
											}

											ptr += 2 * oggInfo.channels;
										}
									}

									int bytesToWrite = 2 * oggInfo.channels * bout;

									if (bytesToWrite >= pcmBuffer.remaining()) {
										throw new IOException("Ogg block too big to be buffered: " + bytesToWrite);
									}

									pcmBuffer.put(convbuffer, 0, bytesToWrite);
									wrote = true;
									dspState.synthesis_read(bout);
								}
							}
						}

						if (page.eos() != 0) {
							this.endOfBitStream = true;
						}

						if (!endOfBitStream && wrote) {
							return;
						}
					}
				}

				if (!endOfBitStream) {
					int index = syncState.buffer(Bufferer.COMMON_SIZE);

					this.bytes = 0;

					if (index >= 0) {
						this.buffer = syncState.data;

						try {
							bytes = input.read(buffer, index, Bufferer.COMMON_SIZE);
						} catch (IOException exception) {
							throw new IOException("Error during Vorbis decoding.");
						}
					} else {
						this.bytes = 0;
					}

					syncState.wrote(bytes);

					if (bytes == 0) {
						this.endOfBitStream = true;
					}
				}
			}

			streamState.clear();
			vorbisBlock.clear();
			dspState.clear();
			oggInfo.clear();
		}

		syncState.clear();
		this.endOfStream = true;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy