/*
* Copyright (c) 2008, 2009, 2010 Denis Tulskiy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with this work. If not, see <http://www.gnu.org/licenses/>.
*/
package com.tulskiy.musique.audio.formats.ogg;
import com.tulskiy.musique.audio.Encoder;
import com.tulskiy.musique.system.configuration.Configuration;
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 java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.logging.Level;
/**
* Based on sample VorbisEncoder from vorbis-java
* <p/>
* Author: Denis Tulskiy
* Date: Jul 26, 2010
*/
public class VorbisEncoder implements Encoder {
private ogg_stream_state os; // take physical pages, weld into a logical stream of packets
private ogg_page og; // one Ogg bitstream page. Vorbis packets are inside
private ogg_packet op; // one raw packet of data for decode
private vorbis_dsp_state vd; // central working state for the packet->PCM decoder
private vorbis_block vb; // local working space for packet->PCM decode
private FileOutputStream output;
private static final float DEFAULT_BITRATE = 0.3f;
@Override
public boolean open(File outputFile, AudioFormat fmt, Configuration options) {
vorbis_info vi = new vorbis_info();
vorbisenc encoder = new vorbisenc();
float quality = DEFAULT_BITRATE;
if (options != null) {
quality = options.getFloat("encoder.vorbis.quality", DEFAULT_BITRATE);
}
logger.log(Level.INFO, "Starting encoding with {0} channels, {1} Hz, quality: {2}",
new Object[]{fmt.getChannels(), fmt.getSampleRate(), quality});
if (!encoder.vorbis_encode_init_vbr(vi, fmt.getChannels(), (int) fmt.getSampleRate(), quality)) {
logger.warning("Failed to Initialize vorbisenc");
return false;
}
vorbis_comment vc = new vorbis_comment();
vc.vorbis_comment_add_tag("ENCODER", "Java Vorbis Encoder");
vd = new vorbis_dsp_state();
if (!vd.vorbis_analysis_init(vi)) {
System.out.println("Failed to Initialize vorbis_dsp_state");
return false;
}
vb = new vorbis_block(vd);
java.util.Random generator = new java.util.Random(); // need to randomize seed
os = 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();
vd.vorbis_analysis_headerout(vc, header, header_comm, header_code);
os.ogg_stream_packetin(header); // automatically placed in its own page
os.ogg_stream_packetin(header_comm);
os.ogg_stream_packetin(header_code);
og = new ogg_page();
op = new ogg_packet();
try {
output = new FileOutputStream(outputFile);
if (!os.ogg_stream_flush(og))
return false;
output.write(og.header, 0, og.header_len);
output.write(og.body, 0, og.body_len);
return true;
} catch (Exception ignored) {
}
return false;
}
@Override
public void encode(byte[] buf, int len) {
try {
int i;
// expose the buffer to submit data
float[][] buffer = vd.vorbis_analysis_buffer(len / 4);
// uninterleave samples
for (i = 0; i < len / 4; i++) {
buffer[0][vd.pcm_current + i] = ((buf[i * 4 + 1] << 8) | (0x00ff & (int) buf[i * 4])) / 32768.f;
buffer[1][vd.pcm_current + i] = ((buf[i * 4 + 3] << 8) | (0x00ff & (int) buf[i * 4 + 2])) / 32768.f;
}
// tell the library how much we actually submitted
vd.vorbis_analysis_wrote(i);
analyze();
} catch (IOException e) {
e.printStackTrace();
}
}
private void analyze() throws IOException {
// vorbis does some data preanalysis, then divvies up blocks for more involved
// (potentially parallel) processing. Get a single block for encoding now
boolean eos = false;
while (vb.vorbis_analysis_blockout(vd)) {
// analysis, assume we want to use bitrate management
vb.vorbis_analysis(null);
vb.vorbis_bitrate_addblock();
while (vd.vorbis_bitrate_flushpacket(op)) {
// weld the packet into the bitstream
os.ogg_stream_packetin(op);
// write out pages (if any)
while (!eos) {
if (!os.ogg_stream_pageout(og)) {
break;
}
output.write(og.header, 0, og.header_len);
output.write(og.body, 0, og.body_len);
// this could be set above, but for illustrative purposes, I do
// it here (to show that vorbis does know where the stream ends)
if (og.ogg_page_eos() > 0)
eos = true;
}
}
}
}
@Override
public void close() {
try {
if (vd != null) {
vd.vorbis_analysis_wrote(0);
analyze();
}
if (output != null)
output.close();
} catch (Exception ignored) {
}
}
}