📄 rateconverter.java
字号:
} else throw new RuntimeException("Unsupported number of channels"); // TODO: check in setInputFormat/setOutputFormat } if (stereoToMono) requiredOutputBufferLength /= 2; else if (monoToStereo) requiredOutputBufferLength *= 2; // get/allocate output buffer: byte[] outputBufferData = (byte []) outputBuffer.getData(); if (outputBufferData == null || outputBufferData.length < requiredOutputBufferLength) { outputBufferData = new byte[requiredOutputBufferLength]; outputBuffer.setData(outputBufferData); } final byte[] inputBufferData = (byte []) inputBuffer.getData(); // so for example if getSampleSizeInBits is 8, this value is 128. final long outputSignedUnsignedDifference = 1 << (outputAudioFormat.getSampleSizeInBits() - 1);// so for example if getSampleSizeInBits is 8, this value is 255. final long outputUnsignedMax = (1 << outputAudioFormat.getSampleSizeInBits()) - 1; final long inputSignedUnsignedDifference = 1 << (inputAudioFormat.getSampleSizeInBits() - 1); // if getSampleSizeInBits is 8, this value is 255: final long inputUnsignedMax = (1 << inputAudioFormat.getSampleSizeInBits()) - 1; // if getSampleSizeInBits is 8, this value is 127: final long inputSignedMax = (1 << (inputAudioFormat.getSampleSizeInBits() - 1)) - 1; // if getSampleSizeInBits is 8, this value is -128: final long inputSignedMin = (inputSignedMax + 1) * -1; double accumulatedRateChangeError = 0.0; // because in the case of non-integral ratios of sample rate change, we have to // output samples occasionally to maintain the correct "slope". This is much // like drawing a diagonal line on the screen. int outputSampleIndex = 0; // index of next sample in output buffer for (int i = 0; i < inputSamples; ++i) { if (stereoToMono && i % 2 == 1) continue; // for now, just omit one channel. TODO: average. // get the ith sample, converted to a long, taking into account sample size in bits, endian-ness, and signed/unsigned. final int byteOffsetOfSample = inputBuffer.getOffset() + i * inputSampleSizeInBytes; final int inputSampleLiteral = getSample(inputBufferData, byteOffsetOfSample, inputSampleSizeInBytes, inputAudioFormat.getEndian()); final long inputSampleLongWithoutSign = UnsignedUtils.uIntToLong(inputSampleLiteral); final long inputSampleLongWithSign; if (inputAudioFormat.getSigned() == AudioFormat.UNSIGNED) inputSampleLongWithSign = inputSampleLongWithoutSign; else if (inputAudioFormat.getSigned() == AudioFormat.SIGNED) { if (inputSampleLongWithoutSign > inputSignedMax) inputSampleLongWithSign = inputSampleLongWithoutSign - inputUnsignedMax - 1; else inputSampleLongWithSign = inputSampleLongWithoutSign; } else throw new RuntimeException("input format signed not specified"); // inputSample is now the literal binary value of the sample (0s in unused MSBs), while inputSampleLong is now the literal numeric value of the sample (reflecting sign if applicable). // now perform some conversions to get the desired output sample: final long outputSampleLongWithSign; // apply sign change difference if needed: if (outputAudioFormat.getSigned() == AudioFormat.SIGNED && inputAudioFormat.getSigned() == AudioFormat.UNSIGNED) outputSampleLongWithSign = inputSampleLongWithSign - inputSignedUnsignedDifference; else if (outputAudioFormat.getSigned() == AudioFormat.UNSIGNED && inputAudioFormat.getSigned() == AudioFormat.SIGNED) outputSampleLongWithSign = inputSampleLongWithSign + inputSignedUnsignedDifference; else outputSampleLongWithSign = inputSampleLongWithSign; // now we have to deal with sample rate issues. we either have more or less samples. If we have more in the output, then // we repeat the input (a better solution would be to smooth). if we have less, we average. if (sampleRateRatio == 1.0) { // no sample rate change: output a single sample for each input sample final long outputSampleLongWithoutSign = getOutputSampleLongWithoutSign(outputSampleLongWithSign, inputUnsignedMax, inputAudioFormat, outputAudioFormat); for (int c = 0; c < outputChannelRepeatCount; ++c) putSample(outputSampleLongWithoutSign, outputBufferData, (outputSampleIndex++) * outputSampleSizeInBytes, outputSampleSizeInBytes, outputAudioFormat.getEndian()); } else if (sampleRateRatio > 1.0) { // more output than input - repeat samples // figure out which channel we are outputting to, so we know which Averager to use: final int outputChannel; if (outputAudioFormat.getChannels() == 1) outputChannel = 0; else outputChannel = i % 2; // this requires that a buffer always has a complete frame, that is, the buffer always starts with channel 0. final Averager a = averagers[outputChannel]; // the input sample can be divided into 2 parts: // 1. the part that will be output as part of the current output samples (combined with whatever has been accumulated) // 2. the rest, which will be accumulated for the next time. // the averager is used as follows: // sampleWeight is expressed as a fraction of the input sample size. final double[] sampleWeights; // TODO: don't allocate array each time. if ((a.numAccumulatedSamples + 1) > 1.0) { // we are going to be averaging 2 input samples for the output sample(s) final double sampleWeight = sampleRateInverseRatio - a.numAccumulatedSamples; final double sampleWeight2 = 1.0 - sampleWeight; sampleWeights = new double[] {sampleWeight, sampleWeight2}; } else { final double sampleWeight = 1.0; sampleWeights = new double[] {sampleWeight}; } for (double sampleWeight : sampleWeights) { a.accumulatedSample += outputSampleLongWithSign * sampleWeight; a.numAccumulatedSamples += sampleWeight; final int repeatCount = (int) (a.numAccumulatedSamples * sampleRateRatio); if (repeatCount > 0) // this will always be > 0, because sampleRateRatio > 1.0 { final long outputSampleLongWithSignAvg = (long) Math.round(a.accumulatedSample / a.numAccumulatedSamples); final long outputSampleLongWithoutSign = getOutputSampleLongWithoutSign(outputSampleLongWithSignAvg, inputUnsignedMax, inputAudioFormat, outputAudioFormat); for (int j = 0; j < repeatCount; ++j) { for (int c = 0; c < outputChannelRepeatCount; ++c) putSample(outputSampleLongWithoutSign, outputBufferData, (outputSampleIndex++) * outputSampleSizeInBytes, outputSampleSizeInBytes, outputAudioFormat.getEndian()); final double oldAccumulatedSamples = a.numAccumulatedSamples; // TODO: this introduces small errors, subtracting each time a.numAccumulatedSamples -= sampleRateInverseRatio; a.accumulatedSample = a.accumulatedSample * a.numAccumulatedSamples / oldAccumulatedSamples; } } } } else if (sampleRateInverseRatio > 1.0) { // more input than output - average samples // figure out which channel we are outputting to, so we know which Averager to use: final int outputChannel; if (outputAudioFormat.getChannels() == 1) outputChannel = 0; else outputChannel = i % 2; // this requires that a buffer always has a complete frame, that is, the buffer always starts with channel 0. final Averager a = averagers[outputChannel]; final double sampleWeight; if ((a.numAccumulatedSamples + 1) * sampleRateRatio > 1.0) // if we will output a sample, and another full sample would spill over into the output sample after this one sampleWeight = sampleRateInverseRatio - a.numAccumulatedSamples; // on a scale from 0 to 1, how much of the new input sample overlaps the output sample else sampleWeight = 1.0; a.accumulatedSample += outputSampleLongWithSign * sampleWeight; a.numAccumulatedSamples += sampleWeight; final boolean doOutput = a.numAccumulatedSamples * sampleRateRatio >= 1.0; if (doOutput) { final long outputSampleLongWithSignAvg = (long) Math.round(a.accumulatedSample / a.numAccumulatedSamples); a.accumulatedSample = outputSampleLongWithSignAvg * (1.0 - sampleWeight); // un-averaged part of current sample a.numAccumulatedSamples = (1.0 - sampleWeight); final long outputSampleLongWithoutSign = getOutputSampleLongWithoutSign(outputSampleLongWithSignAvg, inputUnsignedMax, inputAudioFormat, outputAudioFormat); for (int c = 0; c < outputChannelRepeatCount; ++c) putSample(outputSampleLongWithoutSign, outputBufferData, (outputSampleIndex++) * outputSampleSizeInBytes, outputSampleSizeInBytes, outputAudioFormat.getEndian()); } } } outputBuffer.setLength(outputSampleIndex * outputSampleSizeInBytes); outputBuffer.setOffset(0); outputBuffer.setFormat(outputFormat); final int result = BUFFER_PROCESSED_OK; if (TRACE) { dump("input ", inputBuffer); dump("output", outputBuffer); System.out.println("Result=" + MediaCGUtils.plugInResultToStr(result)); } return result; } private double fractional(double d) { return d - (Math.floor(d)); } private static long getOutputSampleLongWithoutSign(long outputSampleLongWithSign, long inputUnsignedMax, AudioFormat inputAudioFormat, AudioFormat outputAudioFormat) { // here we want -1 to become 255 for an 8-bit value. long outputSampleLongWithoutSign; if (outputSampleLongWithSign >= 0) outputSampleLongWithoutSign = outputSampleLongWithSign; else outputSampleLongWithoutSign = inputUnsignedMax + 1 + outputSampleLongWithSign; return getOutputSampleLongWithoutSign(outputSampleLongWithoutSign, inputAudioFormat, outputAudioFormat); } private static long getOutputSampleLongWithoutSign(long outputSampleLongWithoutSign, AudioFormat inputAudioFormat, AudioFormat outputAudioFormat) { // do calculation with unsigned long, so that sign bits are not shifted in. // apply sample size (truncates, does not round, when going to smaller sample size) if (outputAudioFormat.getSampleSizeInBits() > inputAudioFormat.getSampleSizeInBits()) { outputSampleLongWithoutSign <<= (outputAudioFormat.getSampleSizeInBits() - inputAudioFormat.getSampleSizeInBits()); } else if (inputAudioFormat.getSampleSizeInBits() > outputAudioFormat.getSampleSizeInBits()) { outputSampleLongWithoutSign >>= (inputAudioFormat.getSampleSizeInBits() - outputAudioFormat.getSampleSizeInBits()); } return outputSampleLongWithoutSign; } /** bit-wise literal. Is in general unsigned, but may be signed if all 32 bits are used. */ private static int getSample(byte[] inputBufferData, int byteOffsetOfSample, int inputSampleSizeInBytes, int inputEndian) { int sample = 0; for (int j = 0; j < inputSampleSizeInBytes; ++j) { // offset within sample handles endian-ness: final int offsetWithinSample = inputEndian == AudioFormat.BIG_ENDIAN ? j : (inputSampleSizeInBytes - 1 - j); final byte b = inputBufferData[byteOffsetOfSample + offsetWithinSample]; sample <<= 8; sample |= b & 0xff; } // handle signed-ness. return sample; } private static void putSample(long sampleLong, byte[] inputBufferData, int byteOffsetOfSample, int outputSampleSizeInBytes, int outputEndian) { // handle signed-ness. int sample = (int) sampleLong; for (int j = 0; j < outputSampleSizeInBytes; ++j) { // offset within sample handles endian-ness: final int offsetWithinSample = outputEndian == AudioFormat.LITTLE_ENDIAN ? j : (outputSampleSizeInBytes - 1 - j); final byte b = (byte) ((sample >> (8 * j)) & 0xff); try { inputBufferData[byteOffsetOfSample + offsetWithinSample] = b; } catch (ArrayIndexOutOfBoundsException e) { throw e; } } } public Format setInputFormat(Format arg0) { // TODO: force sample size, etc if (TRACE) System.out.println("setInputFormat: " + MediaCGUtils.formatToStr(arg0)); return super.setInputFormat(arg0); } public Format setOutputFormat(Format arg0) { if (TRACE) System.out.println("setOutputFormat: " + MediaCGUtils.formatToStr(arg0)); return super.setOutputFormat(arg0); } }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -