📄 rateconverter.java
字号:
package net.sf.fmj.media.codec.audio;import java.util.ArrayList;import java.util.List;import java.util.logging.Logger;import javax.media.Buffer;import javax.media.Format;import javax.media.format.AudioFormat;import net.sf.fmj.codegen.MediaCGUtils;import net.sf.fmj.media.AbstractCodec;import net.sf.fmj.utility.LoggerSingleton;import net.sf.fmj.utility.UnsignedUtils;/** * * Converts between different linear audio formats. Able to change signed/unsigned, endian-ness, bits per sample, sample rate, channels. * TODO: optimize. * TODO: improve quality of conversions. See for example http://www.fmjsoft.com/atquality.html (name fmjsoft is coincidental). * TODO: change stereo to mono by averaging, rather than by omission of 2nd channel. * TODO: handle data types besides byte arrays * This converter has so many formats that it causes a big slowdown in filter graph building, both for FMJ and JMF. * @author Ken Larson * */public class RateConverter extends AbstractCodec{ private static final boolean ONLY_CHANGE_1_PARAMETER = false; // TODO: more efficient if false. Still needs testing to make sure changing multiple works. private static final Logger logger = LoggerSingleton.logger; public String getName() { return "Rate Converter"; } public RateConverter() { super(); this.inputFormats = new Format[] { new AudioFormat(AudioFormat.LINEAR, -1.0, -1, -1, -1, -1, -1, -1.0, Format.byteArray) }; } // TODO: move to base class? protected Format[] outputFormats = new Format[] {new AudioFormat(AudioFormat.LINEAR, -1.0, -1, -1, -1, -1, -1, -1.0, Format.byteArray)}; // supported parameter values for conversions: // common audio sample rates. Others could be added with no impact. private static final double[] sampleRateValues = new double[] {8000.0, 11025.0, 22050.0, 44100.0, 48000.0}; private static final int[] sampleSizesInBits = new int[] {8, 16, 24, 32}; // TODO: test 24 and 32. private static final int[] endianValues = new int[] {AudioFormat.BIG_ENDIAN, AudioFormat.LITTLE_ENDIAN}; private static final int[] signedValues = new int[] {AudioFormat.SIGNED, AudioFormat.UNSIGNED}; private static final int[] channelsValues = new int[] {1, 2}; public Format[] getSupportedOutputFormats(Format input) { if (input == null) return outputFormats; else { if (!(input instanceof AudioFormat)) { logger.warning(this.getClass().getSimpleName() + ".getSupportedOutputFormats: input format does not match, returning format array of {null} for " + input); // this can cause an NPE in JMF if it ever happens. return new Format[] {null}; } final AudioFormat inputCast = (AudioFormat) input; if (!inputCast.getEncoding().equals(AudioFormat.LINEAR) || (inputCast.getDataType() != null && inputCast.getDataType() != Format.byteArray) ) { logger.warning(this.getClass().getSimpleName() + ".getSupportedOutputFormats: input format does not match, returning format array of {null} for " + input); // this can cause an NPE in JMF if it ever happens. return new Format[] {null}; } final List resultList = new ArrayList(); // copy this.sampleRateValues, add input sample rate to it, in case it is not in the supported list. final double[] sampleRateValues = new double[RateConverter.sampleRateValues.length + 1]; sampleRateValues[0] = inputCast.getSampleRate(); for (int i = 0; i < RateConverter.sampleRateValues.length; ++i) sampleRateValues[i+1] = RateConverter.sampleRateValues[i]; for (int i = 0; i < sampleRateValues.length; ++i) { final double sampleRate = sampleRateValues[i]; final boolean sampleRateChanged = sampleRate != inputCast.getSampleRate(); for (int j = 0; j < sampleSizesInBits.length; ++j) { final int sampleSizeInBits = sampleSizesInBits[j]; final boolean sampleSizeInBitsChanged = sampleSizeInBits != inputCast.getSampleSizeInBits(); for (int k = 0; k < endianValues.length; ++k) { // endian is only meaningful if there are more than 8 bits final int endian = sampleSizeInBits <= 8 ? inputCast.getEndian() : endianValues[k]; final boolean endianChanged = (sampleSizeInBits > 8 && inputCast.getSampleSizeInBits() > 8) && // only consider endian changed if neither of the formsts is 8 bits endian != inputCast.getEndian(); for (int m = 0; m < signedValues.length; ++m) { final int signed = signedValues[m]; final boolean signedChanged = signed != inputCast.getSigned(); for (int n = 0; n < channelsValues.length; ++n) { final int channels = channelsValues[n]; final boolean channelsChanged = channels != inputCast.getChannels(); int numChanged = 0; if (sampleRateChanged) ++numChanged; if (sampleSizeInBitsChanged) ++numChanged; if (endianChanged) ++numChanged; if (signedChanged) ++numChanged; if (channelsChanged) ++numChanged; if (numChanged < 1) continue; if (ONLY_CHANGE_1_PARAMETER && numChanged != 1) continue; { AudioFormat f = new AudioFormat(AudioFormat.LINEAR, sampleRate, sampleSizeInBits, channels, endian, signed, channels * sampleSizeInBits, sampleRate, Format.byteArray); // Note: for linear, frame size in bits is always the same as for sample size in bits * the number of channels. if (!resultList.contains(f)) resultList.add(f); } } } } } } final Format[] result = new Format[resultList.size()]; for (int i = 0; i < resultList.size(); ++i) result[i] = (Format) resultList.get(i); return result; } } public void open() { for (Averager a : averagers) a.reset(); } public void close() { } private static final boolean TRACE = false; private final Averager averagers[] = new Averager[] {new Averager(), new Averager()}; private static class Averager { public double accumulatedSample = 0.0; // used only for averaging, if output sample rate is less than input public double numAccumulatedSamples = 0.0; // ditto public void reset() { accumulatedSample = 0.0; numAccumulatedSamples = 0.0; } } public int process(Buffer inputBuffer, Buffer outputBuffer) { //if (TRACE) dump("input ", inputBuffer); if (!checkInputBuffer(inputBuffer)) { return BUFFER_PROCESSED_FAILED; } if (isEOM(inputBuffer)) { propagateEOM(outputBuffer); // TODO: what about data? can there be any? return BUFFER_PROCESSED_OK; } // TODO: check in checkInputBuffer? if (!inputBuffer.getFormat().equals(inputFormat)) { throw new RuntimeException("Expected inputBuffer.getFormat().equals(inputFormat): [" + inputBuffer.getFormat() + "] [" + inputFormat + "]"); } final AudioFormat inputAudioFormat = ((AudioFormat) inputBuffer.getFormat()); final AudioFormat outputAudioFormat = (AudioFormat) outputFormat; final boolean sampleRateChanged = inputAudioFormat.getSampleRate() != outputAudioFormat.getSampleRate(); final boolean sampleSizeInBitsChanged = inputAudioFormat.getSampleSizeInBits() != outputAudioFormat.getSampleSizeInBits(); final boolean endianChanged = (outputAudioFormat.getSampleSizeInBits() > 8 && inputAudioFormat.getSampleSizeInBits() > 8) && // only consider endian changed if neither of the formats is 8 bits inputAudioFormat.getEndian() != outputAudioFormat.getEndian(); final boolean signedChanged = inputAudioFormat.getSigned() != outputAudioFormat.getSigned(); final boolean channelsChanged = inputAudioFormat.getChannels() != outputAudioFormat.getChannels(); //System.out.println("RateConvert \n\t\t from: " + inputFormat + "\n\t\t to:" + outputFormat); int numChanged = 0; if (sampleRateChanged) ++numChanged; if (sampleSizeInBitsChanged) ++numChanged; if (endianChanged) ++numChanged; if (signedChanged) ++numChanged; if (channelsChanged) ++numChanged; if (ONLY_CHANGE_1_PARAMETER && numChanged > 1) { // in this case, we only support doing one thing in a particular instance of this codec. // this keeps it simple. Converting multiple format attributes is achieved then by chaining multiple instances // of this codec, with different input and output formats. logger.warning("Input format: " + inputAudioFormat); logger.warning("Output format: " + outputAudioFormat); throw new RuntimeException("Expected RateConverter to change no more than one attribute: " + numChanged + "; sampleRateChanged=" + sampleRateChanged + " sampleSizeInBitsChanged=" + sampleSizeInBitsChanged + " endianChanged=" + endianChanged + " signedChanged=" + signedChanged); } // TODO: check these in setInputFormat and setOutputFormat if (outputAudioFormat.getSampleSizeInBits() % 8 != 0) throw new RuntimeException("RateConverter only supports output sample sizes that are a multiple of 8: " + outputAudioFormat.getSampleSizeInBits()); if (inputAudioFormat.getSampleSizeInBits() % 8 != 0) throw new RuntimeException("RateConverter only supports input sample sizes that are a multiple of 8: " + inputAudioFormat.getSampleSizeInBits()); if (inputAudioFormat.getSampleSizeInBits() > 8 && inputAudioFormat.getEndian() == -1) throw new RuntimeException("RateConverter (input format) requires endian to be specified if sample size in bits is greater than 8"); if (outputAudioFormat.getSampleSizeInBits() > 8 && outputAudioFormat.getEndian() == -1) throw new RuntimeException("RateConverter (output format) requires endian to be specified if sample size in bits is greater than 8"); // if (inputAudioFormat.getFrameSizeInBits() != inputAudioFormat.getChannels() * inputAudioFormat.getSampleSizeInBits())// throw new RuntimeException("Expected (input format) frame size in bits to be sample size in bits * number of channels");// if (outputAudioFormat.getFrameSizeInBits() != outputAudioFormat.getChannels() * outputAudioFormat.getSampleSizeInBits())// throw new RuntimeException("Expected (output format) frame size in bits to be sample size in bits * number of channels"); // output : input final double sampleRateRatio = outputAudioFormat.getSampleRate() / inputAudioFormat.getSampleRate(); final int sampleRateRatioWhole = (int) sampleRateRatio; final double sampleRateRatioRemainder = sampleRateRatio - sampleRateRatioWhole; // TODO: this is most certainly wrong. // input : output final double sampleRateInverseRatio = inputAudioFormat.getSampleRate() / outputAudioFormat.getSampleRate(); final int sampleRateInverseRatioWhole = (int) sampleRateInverseRatio; final int sampleRateInverseRatioCeil = (int) Math.ceil(sampleRateInverseRatio); // sampleRateAveragingErrorIncrement is the amount that our sample rate error increases with each input sample. // say input sample rate is 22050 and output is 8000. Then this is 2.76:1. // without compensation, we will output a sample for every 3 input samples. So we need to output an extra sample // every so often to bring the output to 2.76 for every 3. So 3-2.76 is the error in the "input" side. To // convert this to an error in the "output" side, we have to divide by the ratio, or 2.76 (helps to draw triangles to visualize this). So the increment is (3-2.76)/2.76. final double sampleRateAveragingErrorIncrement = (sampleRateInverseRatioCeil - sampleRateInverseRatio) / sampleRateInverseRatio; final int inputSampleSizeInBytes = inputAudioFormat.getSampleSizeInBits() / 8; final int inputSamples = inputBuffer.getLength() / inputSampleSizeInBytes; // this implies that we will only process whole samples in the input buffer. final int outputSampleSizeInBytes = outputAudioFormat.getSampleSizeInBits() / 8; int requiredOutputBufferLength; if (sampleRateChanged) { // allocate 1 extra output sample, for rounding errors. The actual number of bytes put in the buffer will be correct, based on outputSampleIndex * outputSampleSizeInBytes requiredOutputBufferLength = (int) ((inputSamples * outputSampleSizeInBytes) * Math.ceil(sampleRateRatio)); // TODO: rounds way up// TODO: what if this truncates some samples? } else if (sampleSizeInBitsChanged) { // we only support changing of sample sizes requiredOutputBufferLength = inputSamples * outputSampleSizeInBytes; // TODO: what if this truncates some samples? } else requiredOutputBufferLength = inputBuffer.getLength(); // same number of bytes output as input boolean stereoToMono = false; boolean monoToStereo = false; int outputChannelRepeatCount = 1; // equal to 2 only if monoToStereo is true. if (channelsChanged) { if (inputAudioFormat.getChannels() == 2 && outputAudioFormat.getChannels() == 1) stereoToMono = true; else if (inputAudioFormat.getChannels() == 1 && outputAudioFormat.getChannels() == 2) { outputChannelRepeatCount = 2; monoToStereo = true;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -