blob: 47ef2d451c06c1fb69120d5f75ec5e2be5c145c0 [file] [log] [blame]
package com.google.libvorbis;
import com.google.libogg.OggPacket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.util.LinkedList;
import java.util.Queue;
/**
* This class wraps Vorbis and Ogg JNI
*/
public class VorbisEncoderWrapper {
private VorbisDspState dsp_state_;
private VorbisInfo info_;
private VorbisBlock block_;
private OggPacket ident_packet;
private OggPacket comments_packet;
private OggPacket setup_packet;
VorbisEncoderConfig config = null;
private long lastGranulepos;
private Queue<OggPacket> vorbisPackets;
public VorbisEncoderWrapper() {
info_ = new VorbisInfo();
dsp_state_ = new VorbisDspState();
block_ = new VorbisBlock();
ident_packet = null;
comments_packet = null;
setup_packet = null;
lastGranulepos = 0;
}
public boolean Init(VorbisEncoderConfig configArg) {
config = new VorbisEncoderConfig(configArg);
if (config.channels <= 0 || config.channels > 2)
return false;
if (config.format_tag != VorbisEncoderConfig.Format.kAudioFormatPcm)
return false;
if (config.format_tag == VorbisEncoderConfig.Format.kAudioFormatPcm &&
config.bits_per_sample != 16)
return false;
Codec.vorbisInfoInit(info_);
int status =
VorbisEnc.vorbisEncodeSetupManaged(info_, config.channels, config.sample_rate,
config.minimum_bitrate, config.average_bitrate, config.maximum_bitrate);
if (status != 0)
return false;
if (config.minimum_bitrate == -1 && config.maximum_bitrate == -1 &&
config.bitrate_based_quality) {
// Enable VBR.
if (!CodecControlSet(VorbisEnc.OV_ECTL_RATEMANAGE2_SET, 0)) {
return false;
}
}
status = VorbisEnc.vorbisEncodeSetupInit(info_);
if (status != 0)
return false;
status = Codec.vorbisAnalysisInit(dsp_state_, info_);
if (status != 0)
return false;
status = Codec.vorbisBlockInit(dsp_state_, block_);
if (status != 0)
return false;
status = GenerateHeaders();
if (status != 0)
return false;
config.format_tag = VorbisEncoderConfig.Format.kAudioFormatVorbis;
vorbisPackets = new LinkedList<OggPacket>();
return true;
}
public ByteBuffer getCodecPrivate() {
// Perform minimal private data validation.
if (ident_packet == null || comments_packet == null || setup_packet == null)
return null;
if (ident_packet.getBytes() > 255 || comments_packet.getBytes() > 255)
return null;
final long data_length =
ident_packet.getBytes() + comments_packet.getBytes() + setup_packet.getBytes();
// Calculate total bytes of storage required for the private data chunk.
// 1 byte to store header count (total headers - 1 = 2).
// 1 byte each for ident and comment length values.
// The length of setup data is implied by the total length.
final int header_length = 1 + 1 + 1 + (int)data_length;
ByteBuffer data = ByteBuffer.allocate(header_length);
// Write header count. As above, number of headers - 1.
data.put((byte)2);
// Write ident length, comment length.
data.put((byte)ident_packet.getBytes());
data.put((byte)comments_packet.getBytes());
// Write the data blocks.
byte[] indentData = ident_packet.getPacketData();
data.put(indentData);
byte[] commentsData = comments_packet.getPacketData();
data.put(commentsData);
byte[] setupData = setup_packet.getPacketData();
data.put(setupData);
return data;
}
public boolean encodeAudio(byte[] pcmArray) {
// TODO(fgalligan): Check mod block_align
final int channels = config.channels;
final int num_blocks = pcmArray.length / config.block_align;
ByteBuffer byteBuffer = ByteBuffer.wrap(pcmArray);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
ShortBuffer sampleBuffer = byteBuffer.asShortBuffer();
float[][] float2dArray = new float[channels][num_blocks];
for (int i = 0; i < num_blocks; ++i) {
for (int c = 0; c < channels; ++c) {
short sample = sampleBuffer.get();
float2dArray[c][i] = sample / 32768.f;
}
}
Codec.vorbisAnalysisBuffer(dsp_state_, float2dArray);
int status = Codec.vorbisAnalysisWrote(dsp_state_, num_blocks);
if (status != 0)
return false;
return true;
}
public AudioFrame readCompressedAudio() {
if (samplesEncoded()) {
// There's a compressed block available-- give libvorbis a chance to
// optimize distribution of data for the current encode settings.
OggPacket packet = new OggPacket();
int status = Codec.vorbisAnalysis(block_, packet);
if (status != 0)
return null;
status = Codec.vorbisBitrateAddblock(block_);
if (status != 0)
return null;
while ((status = Codec.vorbisBitrateFlushpacket(dsp_state_, packet)) == 1) {
//Using the add method to add items.
//Should anything go wrong an exception will be thrown.
if (packet.getGranulepos() > 0)
vorbisPackets.add(packet);
}
}
// Calculate size of data.
int totalSize = 0;
for(OggPacket p : vorbisPackets){
totalSize += p.getBytes();
}
if (totalSize == 0)
return null;
ByteBuffer data = ByteBuffer.allocate(totalSize);
long timestamp = (long)samplesToMilliseconds(lastGranulepos);
while (vorbisPackets.size() > 0) {
OggPacket packet = vorbisPackets.remove();
lastGranulepos = packet.getGranulepos();
byte[] packetData = packet.getPacketData();
data.put(packetData);
}
AudioFrame frame = new AudioFrame(timestamp, data);
return frame;
}
//When |SamplesAvailable()| returns true, the user must consume all samples
//made available by libvorbis. Any compressed samples left unconsumed will be
//lost.
private boolean samplesEncoded() {
final int kSamplesAvailable = 1;
int status = Codec.vorbisAnalysisBlockout(dsp_state_, block_);
if (status == kSamplesAvailable) {
return true;
}
return false;
}
private int GenerateHeaders() {
VorbisComment comments = new VorbisComment();
Codec.vorbisCommentInit(comments);
// Add app name and version to vorbis comments.
String encoder_id = "WebM JNI bindings";
String kVorbisEncoderTag = "encoder";
Codec.vorbisCommentAddTag(comments, kVorbisEncoderTag, encoder_id);
// Generate the vorbis header packets.
ident_packet = new OggPacket();
comments_packet = new OggPacket();
setup_packet = new OggPacket();
int status =
Codec.vorbisAnalysisHeaderout(dsp_state_, comments, ident_packet, comments_packet,
setup_packet);
if (status != 0) return status;
return 0;
}
private boolean CodecControlSet(int control_id, int val) {
int status = 0;
if (control_id == VorbisEnc.OV_ECTL_RATEMANAGE2_SET && val == 0) {
// Special case disabling rate control-- libvorbis expects a NULL
// pointer.
status = VorbisEnc.vorbisEncodeCtlSetNull(info_, control_id);
} else {
status = VorbisEnc.vorbisEncodeCtlSetInt(info_, control_id, val);
}
if (status != 0)
return false;
return true;
}
private double samplesToMilliseconds(long numSamples) {
final double sample_rate = config.sample_rate;
double seconds = 0.0;
if (sample_rate != 0) {
seconds = numSamples / sample_rate;
}
return seconds * 1000.0;
}
}