229 lines
		
	
	
	
		
			6.3 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			229 lines
		
	
	
	
		
			6.3 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.client.sounds;
 | |
| 
 | |
| 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 it.unimi.dsi.fastutil.floats.FloatConsumer;
 | |
| import java.io.IOException;
 | |
| import java.io.InputStream;
 | |
| import javax.sound.sampled.AudioFormat;
 | |
| import net.fabricmc.api.EnvType;
 | |
| import net.fabricmc.api.Environment;
 | |
| import org.jetbrains.annotations.Nullable;
 | |
| 
 | |
| @Environment(EnvType.CLIENT)
 | |
| public class JOrbisAudioStream implements FloatSampleSource {
 | |
| 	private static final int BUFSIZE = 8192;
 | |
| 	private static final int PAGEOUT_RECAPTURE = -1;
 | |
| 	private static final int PAGEOUT_NEED_MORE_DATA = 0;
 | |
| 	private static final int PAGEOUT_OK = 1;
 | |
| 	private static final int PACKETOUT_ERROR = -1;
 | |
| 	private static final int PACKETOUT_NEED_MORE_DATA = 0;
 | |
| 	private static final int PACKETOUT_OK = 1;
 | |
| 	private final SyncState syncState = new SyncState();
 | |
| 	private final Page page = new Page();
 | |
| 	private final StreamState streamState = new StreamState();
 | |
| 	private final Packet packet = new Packet();
 | |
| 	private final Info info = new Info();
 | |
| 	private final DspState dspState = new DspState();
 | |
| 	private final Block block = new Block(this.dspState);
 | |
| 	private final AudioFormat audioFormat;
 | |
| 	private final InputStream input;
 | |
| 	private long samplesWritten;
 | |
| 	private long totalSamplesInStream = Long.MAX_VALUE;
 | |
| 
 | |
| 	public JOrbisAudioStream(InputStream input) throws IOException {
 | |
| 		this.input = input;
 | |
| 		Comment comment = new Comment();
 | |
| 		Page page = this.readPage();
 | |
| 		if (page == null) {
 | |
| 			throw new IOException("Invalid Ogg file - can't find first page");
 | |
| 		} else {
 | |
| 			Packet packet = this.readIdentificationPacket(page);
 | |
| 			if (isError(this.info.synthesis_headerin(comment, packet))) {
 | |
| 				throw new IOException("Invalid Ogg identification packet");
 | |
| 			} else {
 | |
| 				for (int i = 0; i < 2; i++) {
 | |
| 					packet = this.readPacket();
 | |
| 					if (packet == null) {
 | |
| 						throw new IOException("Unexpected end of Ogg stream");
 | |
| 					}
 | |
| 
 | |
| 					if (isError(this.info.synthesis_headerin(comment, packet))) {
 | |
| 						throw new IOException("Invalid Ogg header packet " + i);
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				this.dspState.synthesis_init(this.info);
 | |
| 				this.block.init(this.dspState);
 | |
| 				this.audioFormat = new AudioFormat(this.info.rate, 16, this.info.channels, true, false);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private static boolean isError(int value) {
 | |
| 		return value < 0;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public AudioFormat getFormat() {
 | |
| 		return this.audioFormat;
 | |
| 	}
 | |
| 
 | |
| 	private boolean readToBuffer() throws IOException {
 | |
| 		int i = this.syncState.buffer(8192);
 | |
| 		byte[] bs = this.syncState.data;
 | |
| 		int j = this.input.read(bs, i, 8192);
 | |
| 		if (j == -1) {
 | |
| 			return false;
 | |
| 		} else {
 | |
| 			this.syncState.wrote(j);
 | |
| 			return true;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	private Page readPage() throws IOException {
 | |
| 		while (true) {
 | |
| 			int i = this.syncState.pageout(this.page);
 | |
| 			switch (i) {
 | |
| 				case -1:
 | |
| 					throw new IllegalStateException("Corrupt or missing data in bitstream");
 | |
| 				case 0:
 | |
| 					if (this.readToBuffer()) {
 | |
| 						break;
 | |
| 					}
 | |
| 
 | |
| 					return null;
 | |
| 				case 1:
 | |
| 					if (this.page.eos() != 0) {
 | |
| 						this.totalSamplesInStream = this.page.granulepos();
 | |
| 					}
 | |
| 
 | |
| 					return this.page;
 | |
| 				default:
 | |
| 					throw new IllegalStateException("Unknown page decode result: " + i);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private Packet readIdentificationPacket(Page page) throws IOException {
 | |
| 		this.streamState.init(page.serialno());
 | |
| 		if (isError(this.streamState.pagein(page))) {
 | |
| 			throw new IOException("Failed to parse page");
 | |
| 		} else {
 | |
| 			int i = this.streamState.packetout(this.packet);
 | |
| 			if (i != 1) {
 | |
| 				throw new IOException("Failed to read identification packet: " + i);
 | |
| 			} else {
 | |
| 				return this.packet;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	private Packet readPacket() throws IOException {
 | |
| 		while (true) {
 | |
| 			int i = this.streamState.packetout(this.packet);
 | |
| 			switch (i) {
 | |
| 				case -1:
 | |
| 					throw new IOException("Failed to parse packet");
 | |
| 				case 0:
 | |
| 					Page page = this.readPage();
 | |
| 					if (page == null) {
 | |
| 						return null;
 | |
| 					}
 | |
| 
 | |
| 					if (!isError(this.streamState.pagein(page))) {
 | |
| 						break;
 | |
| 					}
 | |
| 
 | |
| 					throw new IOException("Failed to parse page");
 | |
| 				case 1:
 | |
| 					return this.packet;
 | |
| 				default:
 | |
| 					throw new IllegalStateException("Unknown packet decode result: " + i);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private long getSamplesToWrite(int samples) {
 | |
| 		long l = this.samplesWritten + samples;
 | |
| 		long m;
 | |
| 		if (l > this.totalSamplesInStream) {
 | |
| 			m = this.totalSamplesInStream - this.samplesWritten;
 | |
| 			this.samplesWritten = this.totalSamplesInStream;
 | |
| 		} else {
 | |
| 			this.samplesWritten = l;
 | |
| 			m = samples;
 | |
| 		}
 | |
| 
 | |
| 		return m;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean readChunk(FloatConsumer output) throws IOException {
 | |
| 		float[][][] fs = new float[1][][];
 | |
| 		int[] is = new int[this.info.channels];
 | |
| 		Packet packet = this.readPacket();
 | |
| 		if (packet == null) {
 | |
| 			return false;
 | |
| 		} else if (isError(this.block.synthesis(packet))) {
 | |
| 			throw new IOException("Can't decode audio packet");
 | |
| 		} else {
 | |
| 			this.dspState.synthesis_blockin(this.block);
 | |
| 
 | |
| 			int i;
 | |
| 			while ((i = this.dspState.synthesis_pcmout(fs, is)) > 0) {
 | |
| 				float[][] gs = fs[0];
 | |
| 				long l = this.getSamplesToWrite(i);
 | |
| 				switch (this.info.channels) {
 | |
| 					case 1:
 | |
| 						copyMono(gs[0], is[0], l, output);
 | |
| 						break;
 | |
| 					case 2:
 | |
| 						copyStereo(gs[0], is[0], gs[1], is[1], l, output);
 | |
| 						break;
 | |
| 					default:
 | |
| 						copyAnyChannels(gs, this.info.channels, is, l, output);
 | |
| 				}
 | |
| 
 | |
| 				this.dspState.synthesis_read(i);
 | |
| 			}
 | |
| 
 | |
| 			return true;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private static void copyAnyChannels(float[][] source, int channels, int[] startIndexes, long samplesToWrite, FloatConsumer output) {
 | |
| 		for (int i = 0; i < samplesToWrite; i++) {
 | |
| 			for (int j = 0; j < channels; j++) {
 | |
| 				int k = startIndexes[j];
 | |
| 				float f = source[j][k + i];
 | |
| 				output.accept(f);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private static void copyMono(float[] source, int startIndex, long samplesToWrite, FloatConsumer output) {
 | |
| 		for (int i = startIndex; i < startIndex + samplesToWrite; i++) {
 | |
| 			output.accept(source[i]);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private static void copyStereo(float[] leftSource, int leftStartIndex, float[] rightSource, int rightStartIndex, long samplesToWrite, FloatConsumer output) {
 | |
| 		for (int i = 0; i < samplesToWrite; i++) {
 | |
| 			output.accept(leftSource[leftStartIndex + i]);
 | |
| 			output.accept(rightSource[rightStartIndex + i]);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void close() throws IOException {
 | |
| 		this.input.close();
 | |
| 	}
 | |
| }
 |