📄 samplerateconversionprovider.java
字号:
} private int roundDown(double a) { //return a<0?((int) (a-0.5f)):((int) (a+0.5f)); //return a<0?(-((int) -a)):((int) a); //return (int) a; return (int) Math.floor(a); } private int roundUp(double a) { //return a<0?((int) (a-0.5f)):((int) (a+0.5f)); //return a<0?(-((int) -a)):((int) a); return (int) Math.ceil(a); } private void convertSampleAndHold( float[] inSamples, double inSampleOffset, int inSampleCount, double increment, float[] outSamples, int outSampleOffset, int outSampleCount, float[] history, int historyLength) { if (DEBUG_STREAM) { TDebug.out("convertSampleAndHold(inSamples["+inSamples.length+"], " +roundDown(inSampleOffset)+" to "+roundDown(inSampleOffset+increment*(outSampleCount-1))+", " +"outSamples["+outSamples.length+"], "+outSampleOffset+" to "+(outSampleOffset+outSampleCount-1)+")"); System.out.flush(); } for (int i=0; i<outSampleCount; i++) { int iInIndex=roundDown(inSampleOffset+increment*i); if (iInIndex<0) { outSamples[i+outSampleOffset]=history[iInIndex+historyLength]; if (DEBUG_STREAM) { TDebug.out("convertSampleAndHold: using history["+(iInIndex+historyLength)+" because inIndex="+iInIndex); } } else if (iInIndex>=inSampleCount) { if (DEBUG_STREAM_PROBLEMS) { TDebug.out("convertSampleAndHold: INDEX OUT OF BOUNDS outSamples["+i+"]=inSamples[roundDown("+inSampleOffset+")="+iInIndex+"];"); } } else { outSamples[i+outSampleOffset]=inSamples[iInIndex]; //outSamples[i]=inSamples[roundDown(inSampleOffset)]; } //inSampleOffset+=increment; <- this produces too much rounding errors... } } private void convertLinearInterpolation( float[] inSamples, double inSampleOffset, int inSampleCount, double increment, float[] outSamples, int outSampleOffset, int outSampleCount, float[] history, int historyLength) { if (DEBUG_STREAM) { TDebug.out("convertLinearInterpolate(inSamples["+inSamples.length+"], " +roundDown(inSampleOffset)+" to "+roundDown(inSampleOffset+increment*(outSampleCount-1))+", " +"outSamples["+outSamples.length+"], "+outSampleOffset+" to "+(outSampleOffset+outSampleCount-1)+")"); System.out.flush(); } for (int i=0; i<outSampleCount; i++) { try { double dInIndex=inSampleOffset+increment*i-1; int iInIndex=(int) Math.floor(dInIndex); double factor=1.0d-(dInIndex-iInIndex); float value=0; for (int x=0; x<2; x++) { if (iInIndex>=inSampleCount) { // we clearly need more samples ! if (DEBUG_STREAM_PROBLEMS) { TDebug.out("linear interpolation: INDEX OUT OF BOUNDS iInIndex="+iInIndex+" inSampleCount="+inSampleCount); } } else if (iInIndex<0) { int histIndex=iInIndex+historyLength; if (histIndex>=0) { value+=history[histIndex]*factor; if (DEBUG_STREAM) { TDebug.out("linear interpolation: using history["+iInIndex+"]"); } } else if (DEBUG_STREAM_PROBLEMS) { TDebug.out("linear interpolation: history INDEX OUT OF BOUNDS iInIndex="+iInIndex+" histIndex="+histIndex+" history length="+historyLength); } } else { value+=inSamples[iInIndex]*factor; } factor=1-factor; iInIndex++; } outSamples[i+outSampleOffset]=value; //outSamples[i]=inSamples[roundDown(inSampleOffset)]; } catch (ArrayIndexOutOfBoundsException aioobe) { if (DEBUG_STREAM_PROBLEMS) { TDebug.out("**** REAL INDEX OUT OF BOUNDS ****** outSamples["+i+"]=inSamples[roundDown("+inSampleOffset+")="+roundDown(inSampleOffset)+"];"); } //throw aioobe; } //inSampleOffset+=increment; <- this produces too much rounding errors... } } private double inSamples2outSamples(double inSamples) { return inSamples*targetSampleRate/sourceSampleRate; } private double outSamples2inSamples(double outSamples) { return outSamples*sourceSampleRate/targetSampleRate; } /** * Main read method. It blocks until all samples are converted or * the source stream is at its end or closed.<br> * The sourceStream's sample rate is converted following * the current setting of <code>conversionAlgorithm</code>. * At most outBuffer.getSampleCount() are converted. In general, * if the return value (and outBuffer.getSampleCount()) is less * after processing this function, then it is an indicator * that it was the last block to be processed. * * @see #setConversionAlgorithm(int) * @param outBuffer the buffer that the converted samples will be written to. * @throws IllegalArgumentException when outBuffer's channel count does not match * @return number of samples in outBuffer ( == outBuffer.getSampleCount()) * or -1. A return value of 0 is only possible when outBuffer has 0 samples. */ public synchronized int read(FloatSampleBuffer outBuffer) throws IOException { if (isClosed()) { return -1; } if (outBuffer.getChannelCount()!=thisBuffer.getChannelCount()) { throw new IllegalArgumentException("passed buffer has different channel count"); } if (outBuffer.getSampleCount()==0) { return 0; } if (TDebug.TraceAudioConverter) { TDebug.out(">SamplerateConverterStream.read("+outBuffer.getSampleCount()+"frames)"); } float[] outSamples; float[] inSamples; float[] history; double increment=outSamples2inSamples(1.0); int writtenSamples=0; do { // check thisBuffer with samples of source stream int inSampleCount=thisBuffer.getSampleCount(); if (roundDown(dPos)>=inSampleCount || !thisBufferValid) { // need to load new data of sourceStream readFromSourceStream(); if (isClosed()) { break; } inSampleCount=thisBuffer.getSampleCount(); } // calculate number of samples to write int writeCount=outBuffer.getSampleCount()-writtenSamples; // check whether this exceeds the current in-buffer if (roundDown(outSamples2inSamples((double) writeCount)+dPos)>=inSampleCount) { int lastOutIndex=roundUp(inSamples2outSamples(((double) inSampleCount)-dPos)); // normally, the above formula gives the exact writeCount. // but due to rounding issues, sometimes it has to be decremented once. // so we need to iterate to get the last index and then increment it once to make // it the writeCount (=the number of samples to write) while (roundDown(outSamples2inSamples((double) lastOutIndex)+dPos)>=inSampleCount) { lastOutIndex--; if (DEBUG_STREAM) { TDebug.out("--------- Decremented lastOutIndex="+lastOutIndex); } } if (DEBUG_STREAM_PROBLEMS) { int testLastOutIndex=writeCount-1; if (DEBUG_STREAM_PROBLEMS) { while (roundDown(outSamples2inSamples((double) testLastOutIndex)+dPos)>=inSampleCount) { testLastOutIndex--; } } if (testLastOutIndex!=lastOutIndex) { TDebug.out("lastOutIndex wrong: lastOutIndex="+lastOutIndex+" testLastOutIndex="+testLastOutIndex+" !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); } } writeCount=lastOutIndex+1; } // finally do the actual conversion - separated per channel for (int channel=0; channel<outBuffer.getChannelCount(); channel++) { inSamples=thisBuffer.getChannel(channel); outSamples=outBuffer.getChannel(channel); history=historyBuffer.getChannel(channel); switch (conversionAlgorithm) { case SAMPLE_AND_HOLD: convertSampleAndHold(inSamples, dPos, inSampleCount, increment, outSamples, writtenSamples, writeCount, history, historyBuffer.getSampleCount()); break; case LINEAR_INTERPOLATION: convertLinearInterpolation(inSamples, dPos, inSampleCount, increment, outSamples, writtenSamples, writeCount, history, historyBuffer.getSampleCount()); break; } } writtenSamples+=writeCount; // adjust new position dPos+=outSamples2inSamples((double) writeCount); } while (!isClosed() && writtenSamples<outBuffer.getSampleCount()); if (writtenSamples<outBuffer.getSampleCount()) { outBuffer.changeSampleCount(writtenSamples, true); } if (TDebug.TraceAudioConverter) { testOutFramesReturned+=outBuffer.getSampleCount(); TDebug.out("< return "+outBuffer.getSampleCount()+"frames. Total="+testOutFramesReturned+" frames. Read total "+testInFramesRead+" frames from source stream"); } return outBuffer.getSampleCount(); } //////////////////// utility methods //////////////////////// protected double sourceFrames2targetFrames(double sourceFrames) { return targetSampleRate/sourceSampleRate*sourceFrames; } protected double targetFrames2sourceFrames(double targetFrames) { return sourceSampleRate/targetSampleRate*targetFrames; } protected long sourceBytes2targetBytes(long sourceBytes) { long sourceFrames=sourceBytes/getSourceFrameSize(); long targetFrames=(long) sourceFrames2targetFrames(sourceFrames); return targetFrames*getFrameSize(); } protected long targetBytes2sourceBytes(long targetBytes) { long targetFrames=targetBytes/getFrameSize(); long sourceFrames=(long) targetFrames2sourceFrames(targetFrames); return sourceFrames*getSourceFrameSize(); } public int getFrameSize() { return getFormat().getFrameSize(); } public int getSourceFrameSize() { return sourceStream.getFormat().getFrameSize(); } //////////////////// methods overwritten of AudioInputStream //////////////////////// public int read() throws IOException { if (getFormat().getFrameSize() != 1) { throw new IOException("frame size must be 1 to read a single byte"); } // very ugly, but efficient. Who uses this method anyway ? byte[] temp = new byte[1]; int result = read(temp); if (result <= 0) { return -1; } return temp[0] & 0xFF; } /** * @see #read(byte[], int, int) */ public int read(byte[] abData) throws IOException { return read(abData, 0, abData.length); } /** * Read nLength bytes that will be the converted samples * of the original inputStream. * When nLength is not an integral number of frames, * this method may read less than nLength bytes. */ public int read(byte[] abData, int nOffset, int nLength) throws IOException { if (isClosed()) { return -1; } int frameCount=nLength/getFrameSize(); if (writeBuffer==null) { writeBuffer=new FloatSampleBuffer(getFormat().getChannels(), frameCount, getFormat().getSampleRate()); } else { writeBuffer.changeSampleCount(frameCount, false); } int writtenSamples=read(writeBuffer); if (writtenSamples==-1) { return -1; } int written=writeBuffer.convertToByteArray(abData, nOffset, getFormat()); return written; } public synchronized long skip(long nSkip) throws IOException { // only returns integral frames long sourceSkip = targetBytes2sourceBytes(nSkip); long sourceSkipped = sourceStream.skip(sourceSkip); flush(); return sourceBytes2targetBytes(sourceSkipped); } public int available() throws IOException { return (int) sourceBytes2targetBytes(sourceStream.available()); } public void mark(int readlimit) { sourceStream.mark((int) targetBytes2sourceBytes(readlimit)); } public synchronized void reset() throws IOException { sourceStream.reset(); flush(); } public boolean markSupported() { return sourceStream.markSupported(); } public void close() throws IOException { if (isClosed()) { return; } sourceStream.close(); // clean memory, this will also be an indicator that // the stream is closed thisBuffer=null; historyBuffer=null; byteBuffer=null; } ///////////////////////////// additional methods ///////////////////////////// public boolean isClosed() { return thisBuffer==null; } /** * Flushes the internal buffers */ public synchronized void flush() { if (!isClosed()) { thisBufferValid=false; historyBuffer.makeSilence(); } } /////////////////////////// Properties /////////////////////////////////////// public synchronized void setTargetSampleRate(float sr) { if (sr>0) { targetSampleRate=sr; //((SRCAudioFormat) getFormat()).setSampleRate(sr); resizeBuffers(); } } public synchronized void setConversionAlgorithm(int algo) { if ((algo==SAMPLE_AND_HOLD || algo==LINEAR_INTERPOLATION) && (algo!=conversionAlgorithm)) { conversionAlgorithm=algo; resizeBuffers(); } } public synchronized float getTargetSampleRate() { return targetSampleRate; } public synchronized int getConversionAlgorithm() { return conversionAlgorithm; } } /** Obviously, this class is used to be able to set the frame rate/sample rate after the AudioFormat object has been created. It assumes the PCM case where the frame rate is always in sync with the sample rate. (MP) */ public static class SRCAudioFormat extends AudioFormat { private float sampleRate; public SRCAudioFormat(AudioFormat targetFormat) { super(targetFormat.getEncoding(), targetFormat.getSampleRate(), targetFormat.getSampleSizeInBits(), targetFormat.getChannels(), targetFormat.getChannels()*targetFormat.getSampleSizeInBits()/8, targetFormat.getSampleRate(), targetFormat.isBigEndian()); this.sampleRate=targetFormat.getSampleRate(); } public void setSampleRate(float sr) { if (sr>0) { this.sampleRate=sr; } } public float getSampleRate() { return this.sampleRate; } public float getFrameRate() { return this.sampleRate; } }}/*** SampleRateConversionProvider.java ***/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -