All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.sheinbergon.aac.encoder.AACAudioEncoder Maven / Gradle / Ivy
package org.sheinbergon.aac.encoder;
import com.sun.jna.Memory;
import com.sun.jna.ptr.IntByReference;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.commons.lang3.Range;
import org.sheinbergon.aac.encoder.util.AACAudioEncoderException;
import org.sheinbergon.aac.encoder.util.AACEncodingChannelMode;
import org.sheinbergon.aac.encoder.util.AACEncodingProfile;
import org.sheinbergon.aac.encoder.util.WAVAudioSupport;
import org.sheinbergon.aac.jna.FdkAACLibFacade;
import org.sheinbergon.aac.jna.structure.AACEncBufDesc;
import org.sheinbergon.aac.jna.structure.AACEncInfo;
import org.sheinbergon.aac.jna.structure.AACEncoder;
import org.sheinbergon.aac.jna.util.AACEncParam;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Optional;
import java.util.Set;
@Accessors(fluent = true)
public class AACAudioEncoder implements AutoCloseable {
/**
* Safe, reasonable boundaries according to @see fdk-aac/libAACenc/include/aacenc_lib.h
*/
private final static Range BITRATE_RANGE = Range.between(64000, 640000);
private final static Set SAMPLE_RATES = Set.of(8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000);
private final static int WAV_INPUT_CHANNEL_ORDER = 1;
private final static int MAX_ENCODER_CHANNELS = 0;
private final static int ENCODER_MODULES_MASK = 0;
// This buffer just needs to be big enough to contain the encoded data
private final static int OUT_BUFFER_SIZE = 20480;
private final AACEncoder encoder;
@Getter
private final int inputBufferSize;
// Hard references are advised for memory buffers
private final Memory inBuffer;
private final Memory outBuffer;
private final AACEncBufDesc inBufferDescriptor;
private final AACEncBufDesc outBufferDescriptor;
private AACAudioEncoder(AACEncoder encoder, AACEncInfo info) {
this.encoder = encoder;
this.inputBufferSize = info.inputChannels * info.frameLength * 2;
this.inBuffer = new Memory(inputBufferSize);
this.outBuffer = new Memory(OUT_BUFFER_SIZE);
this.inBufferDescriptor = FdkAACLibFacade.inBufferDescriptor(inBuffer);
this.outBufferDescriptor = FdkAACLibFacade.outBufferDescriptor(outBuffer);
disableStructureSynchronization();
}
public static Builder builder() {
return new Builder();
}
@Setter
@Accessors(chain = true, fluent = true)
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class Builder {
// Arbitrary quality factor safety measure. This might change in the future
private final static float SAMPLES_TO_BIT_RATE_FACTOR = 1.5f;
// Defaults
private boolean afterBurner = true;
private AACEncodingProfile profile = AACEncodingProfile.AAC_LC;
private int channels = 2;
private int bitRate = 64000;
private int sampleRate = 44100;
private void setEncoderParams(AACEncoder encoder) {
FdkAACLibFacade.setEncoderParam(encoder, AACEncParam.AACENC_AFTERBURNER, afterBurner ? 1 : 0);
FdkAACLibFacade.setEncoderParam(encoder, AACEncParam.AACENC_SAMPLERATE, sampleRate);
FdkAACLibFacade.setEncoderParam(encoder, AACEncParam.AACENC_BITRATE, bitRate);
FdkAACLibFacade.setEncoderParam(encoder, AACEncParam.AACENC_TRANSMUX, 2);
FdkAACLibFacade.setEncoderParam(encoder, AACEncParam.AACENC_AOT, profile.getAot());
FdkAACLibFacade.setEncoderParam(encoder, AACEncParam.AACENC_CHANNELORDER, WAV_INPUT_CHANNEL_ORDER);
FdkAACLibFacade.setEncoderParam(encoder, AACEncParam.AACENC_CHANNELMODE, AACEncodingChannelMode.valueOf(channels).getMode());
}
private void adaptBitRate() {
float minimalBitRate = channels * sampleRate / SAMPLES_TO_BIT_RATE_FACTOR;
bitRate = minimalBitRate > bitRate ? (int) minimalBitRate : bitRate;
}
// TODO - add AAC profile verification
public AACAudioEncoder build() {
adaptBitRate();
if (!SAMPLE_RATES.contains(sampleRate)) {
throw new AACAudioEncoderException("sampleRate", sampleRate);
} else if (!BITRATE_RANGE.contains(bitRate)) {
throw new AACAudioEncoderException("bitRate", bitRate);
} else if (AACEncodingChannelMode.valueOf(channels) == AACEncodingChannelMode.MODE_INVALID) {
throw new AACAudioEncoderException("channels", channels);
} else {
AACEncoder encoder = FdkAACLibFacade.openEncoder(ENCODER_MODULES_MASK, MAX_ENCODER_CHANNELS);
setEncoderParams(encoder);
FdkAACLibFacade.initEncoder(encoder);
AACEncInfo info = FdkAACLibFacade.getEncoderInfo(encoder);
return new AACAudioEncoder(encoder, info);
}
}
}
public final AACAudioOutput encode(WAVAudioInput input, boolean conclude) throws AACAudioEncoderException {
int read;
AACAudioOutput.Accumulator accumlator = AACAudioOutput.accumulator();
try {
ByteArrayInputStream inputStream = new ByteArrayInputStream(input.data());
byte[] buffer = new byte[inputBufferSize()];
while ((read = inputStream.read(buffer)) != WAVAudioSupport.EOS) {
populateInputBuffer(buffer, read);
byte[] encoded = FdkAACLibFacade.encode(encoder, inBufferDescriptor, outBufferDescriptor, read)
.orElseThrow(() -> new IllegalStateException("No encoded audio data returned"));
accumlator.accumulate(encoded);
}
if (conclude) {
accumlator.accumulate(conclude().data());
}
return accumlator.done();
} catch (IOException | RuntimeException x) {
throw new AACAudioEncoderException("Could not encode WAV audio to AAC audio", x);
}
}
public AACAudioOutput conclude() throws AACAudioEncoderException {
Optional optional;
try {
inBufferDescriptor.clear();
AACAudioOutput.Accumulator accumlator = AACAudioOutput.accumulator();
while ((optional = FdkAACLibFacade.encode(encoder, inBufferDescriptor, outBufferDescriptor, WAVAudioSupport.EOS)).isPresent()) {
accumlator.accumulate(optional.get());
}
return accumlator.done();
} catch (RuntimeException x) {
throw new AACAudioEncoderException("Could not conclude WAV audio to AAC audio", x);
}
}
private void populateInputBuffer(byte[] buffer, int size) {
inBuffer.write(0, buffer, 0, size);
if (size != inputBufferSize) {
inBufferDescriptor.bufSizes = new IntByReference(size);
inBufferDescriptor.writeField("bufSizes");
}
}
/*
* This disables non-crucial synchronization for irrelevant structs.
* In order to dramatically boost performance and solve JNA memory pressure issues
*/
private void disableStructureSynchronization() {
encoder.write();
encoder.setAutoSynch(false);
inBufferDescriptor.write();
inBufferDescriptor.setAutoSynch(false);
outBufferDescriptor.write();
outBufferDescriptor.setAutoSynch(false);
}
@Override
public void close() {
FdkAACLibFacade.closeEncoder(encoder);
}
}