package ch.retorte.intervalmusiccompositor.encoder;
import ch.retorte.intervalmusiccompositor.spi.progress.ProgressListener;
import org.xiph.libogg.ogg_packet;
import org.xiph.libogg.ogg_page;
import org.xiph.libogg.ogg_stream_state;
import org.xiph.libvorbis.*;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* <pre>
* ********************************************************************
* * *
* * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. *
* * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS *
* * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
* * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. *
* * *
* * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 *
* * by the Xiph.Org Foundation http://www.xiph.org/ *
* * *
* ********************************************************************
* </pre>
*
* The VorbisEncoder converts a byte array of pcm wave data into the vorbis format in a ogg container.
* <p>
* Code of this class more or less one by one taken from the original VorbisEncoder from here:
* http://downloads.xiph.org/releases/vorbis-java/
*/
class VorbisEncoder {
//---- Static
private static final String ENCODER_TAG_NAME = "ENCODER";
private static final String ENCODER_TAG_CONTENT = "Java Vorbis Encoder";
private static final float HIGH_QUALITY_256_KB = .8f;
//---- Fields
private ogg_stream_state oggStreamState;
private ogg_page oggPage;
private ogg_packet oggPacket;
private vorbis_dsp_state vorbisDspState; // central working state for the packet->PCM decoder
private vorbis_block vorbisBlock; // local working space for packet->PCM decode
private int READ = 1024;
private byte[] readBuffer = new byte[READ * 4 + 44];
private AudioFormat audioFormat;
private ProgressListener progressListener;
//---- Constructor
VorbisEncoder(AudioFormat audioFormat, ProgressListener progressListener) {
this.audioFormat = audioFormat;
this.progressListener = progressListener;
}
//---- Methods
byte[] encodeToOgg(AudioInputStream audioInputStream, long streamLengthInBytes) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
long bytesReadSoFar = 0;
initializeOggStream();
initializeOggPageFor(result);
while (!isOggEndOfStream()) {
int i;
int bytes = audioInputStream.read(readBuffer, 0, READ * 4); // stereo hardwired here
bytesReadSoFar = bytesReadSoFar + bytes;
updateProgressWith(bytesReadSoFar, streamLengthInBytes);
if (0 < bytes) {
// data to encode
// expose the buffer to submit data
float[][] buffer = vorbisDspState.vorbis_analysis_buffer(READ);
// Un-interleave samples
for (i = 0; i < bytes / 4; i++) {
buffer[0][vorbisDspState.pcm_current + i] = ((readBuffer[i * 4 + 1] << 8) | (0x00ff & (int) readBuffer[i * 4])) / 32768.f;
buffer[1][vorbisDspState.pcm_current + i] = ((readBuffer[i * 4 + 3] << 8) | (0x00ff & (int) readBuffer[i * 4 + 2])) / 32768.f;
}
// Tell the library how much we actually submitted
vorbisDspState.vorbis_analysis_wrote(i);
} else {
// end of file. this can be done implicitly in the mainline,
// but it's easier to see here in non-clever fashion.
// Tell the library we're at end of stream so that it can handle
// the last frame and mark end of stream in the output properly
vorbisDspState.vorbis_analysis_wrote(0);
}
// vorbis does some data pre analysis, then divides up blocks for more involved
// (potentially parallel) processing. Get a single block for encoding now
while (vorbisBlock.vorbis_analysis_blockout(vorbisDspState)) {
// analysis, assume we want to use bitrate management
vorbisBlock.vorbis_analysis(null);
vorbisBlock.vorbis_bitrate_addblock();
while (vorbisDspState.vorbis_bitrate_flushpacket(oggPacket)) {
// weld the packet into the bit stream
oggStreamState.ogg_stream_packetin(oggPacket);
// write out pages (if any)
while (!isOggEndOfStream()) {
if (!oggStreamState.ogg_stream_pageout(oggPage)) {
break;
}
result.write(oggPage.header, 0, oggPage.header_len);
result.write(oggPage.body, 0, oggPage.body_len);
}
}
}
}
audioInputStream.close();
result.close();
return result.toByteArray();
}
private void initializeOggStream() throws IOException {
vorbis_info vorbisInfo = new vorbis_info();
vorbisenc encoder = new vorbisenc();
if (!encoder.vorbis_encode_init_vbr(vorbisInfo, audioFormat.getChannels(), (int) audioFormat.getSampleRate(), HIGH_QUALITY_256_KB)) {
throw new IOException("Failed to initialize Vorbis encoder.");
}
vorbis_comment vorbisComment = new vorbis_comment();
vorbisComment.vorbis_comment_add_tag(ENCODER_TAG_NAME, ENCODER_TAG_CONTENT);
vorbisDspState = new vorbis_dsp_state();
if (!vorbisDspState.vorbis_analysis_init(vorbisInfo)) {
throw new IOException("Failed to initialize Vorbis DSP state.");
}
vorbisBlock = new vorbis_block(vorbisDspState);
java.util.Random generator = new java.util.Random(); // need to randomize seed
oggStreamState = new ogg_stream_state(generator.nextInt(256));
ogg_packet header = new ogg_packet();
ogg_packet header_comm = new ogg_packet();
ogg_packet header_code = new ogg_packet();
vorbisDspState.vorbis_analysis_headerout(vorbisComment, header, header_comm, header_code);
oggStreamState.ogg_stream_packetin(header); // automatically placed in its own page
oggStreamState.ogg_stream_packetin(header_comm);
oggStreamState.ogg_stream_packetin(header_code);
}
private void initializeOggPageFor(ByteArrayOutputStream result) {
oggPage = new ogg_page();
oggPacket = new ogg_packet();
while (true) {
if (!oggStreamState.ogg_stream_flush(oggPage))
break;
result.write(oggPage.header, 0, oggPage.header_len);
result.write(oggPage.body, 0, oggPage.body_len);
}
}
private boolean isOggEndOfStream() {
return 0 < oggPage.ogg_page_eos();
}
private void updateProgressWith(long currentPosition, long total) {
progressListener.onProgressUpdate((int) (100.0 / total * currentPosition));
}
}