Android音频降采样:将44100Hz录制音频转512Hz用于卷积运算
Got it, let's break down how to solve this problem—whether you need to downsample your 44100Hz recording after capturing it or do it in real-time while recording for immediate convolution. Here's practical, self-contained code and guidance tailored for Android:
If you're recording first and processing later, this approach is straightforward. The key steps are: apply a low-pass filter to avoid aliasing (critical per Nyquist's theorem), then resample to 512Hz using interpolation for better quality than just skipping samples.
Code Implementation
import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.file.Files; import java.nio.file.Paths; // Step 1: Read your 44100Hz 16-bit mono PCM recording File inputPcmFile = new File(getFilesDir(), "recording_44100.pcm"); byte[] inputBytes = Files.readAllBytes(inputPcmFile.toPath()); short[] inputSamples = new short[inputBytes.length / 2]; ByteBuffer.wrap(inputBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(inputSamples); // Step 2: Apply low-pass filter to remove frequencies above 256Hz (Nyquist for 512Hz) short[] filteredSamples = applyLowPassFilter(inputSamples); // Step 3: Downsample to 512Hz using linear interpolation int targetSampleRate = 512; int sourceSampleRate = 44100; double resampleRatio = (double) sourceSampleRate / targetSampleRate; short[] downsampledSamples = new short[(int) Math.floor(inputSamples.length / resampleRatio)]; for (int i = 0; i < downsampledSamples.length; i++) { double sourceIndex = i * resampleRatio; int lowerIdx = (int) Math.floor(sourceIndex); int upperIdx = Math.min(lowerIdx + 1, filteredSamples.length - 1); double fraction = sourceIndex - lowerIdx; downsampledSamples[i] = (short) ((1 - fraction) * filteredSamples[lowerIdx] + fraction * filteredSamples[upperIdx]); } // Use downsampledSamples for your convolution operation here! // Helper: Simple 7-point FIR low-pass filter (adjust kernel for better quality if needed) private short[] applyLowPassFilter(short[] input) { float[] filterKernel = {0.05f, 0.1f, 0.2f, 0.3f, 0.2f, 0.1f, 0.05f}; int kernelSize = filterKernel.length; int halfKernel = kernelSize / 2; short[] output = new short[input.length]; for (int i = 0; i < input.length; i++) { float sum = 0; for (int j = 0; j < kernelSize; j++) { int sampleIdx = i + j - halfKernel; sampleIdx = Math.max(0, Math.min(sampleIdx, input.length - 1)); // Handle edge cases sum += input[sampleIdx] * filterKernel[j]; } output[i] = (short) Math.round(sum); } return output; }
If you need to use the 512Hz signal immediately for convolution while recording, you can process AudioRecord's input buffer in real-time:
Code Implementation
import android.media.AudioRecord; import android.media.MediaRecorder; import java.util.ArrayList; import java.util.List; private AudioRecord audioRecord; private int sourceSampleRate = 44100; private int targetSampleRate = 512; private double resampleRatio = (double) sourceSampleRate / targetSampleRate; private List<Short> sampleBuffer = new ArrayList<>(); // Initialize and start recording with real-time processing private void startRealTimeRecording() { int minBufferSize = AudioRecord.getMinBufferSize( sourceSampleRate, android.media.AudioFormat.CHANNEL_IN_MONO, android.media.AudioFormat.ENCODING_PCM_16BIT ); audioRecord = new AudioRecord( MediaRecorder.AudioSource.MIC, sourceSampleRate, android.media.AudioFormat.CHANNEL_IN_MONO, android.media.AudioFormat.ENCODING_PCM_16BIT, minBufferSize * 2 // Use larger buffer to avoid underruns ); // Set up periodic callback to process incoming samples audioRecord.setRecordPositionUpdateListener(new AudioRecord.OnRecordPositionUpdateListener() { @Override public void onPeriodicNotification(AudioRecord recorder) { short[] rawBuffer = new short[minBufferSize / 2]; // 16-bit samples int bytesRead = recorder.read(rawBuffer, 0, rawBuffer.length); if (bytesRead > 0) { // Add new samples to buffer for (short s : rawBuffer) sampleBuffer.add(s); // Generate 512Hz samples as soon as we have enough data while (sampleBuffer.size() >= resampleRatio) { double sourceIndex = 0; int lowerIdx = (int) Math.floor(sourceIndex); int upperIdx = Math.min(lowerIdx + 1, sampleBuffer.size() - 1); double fraction = sourceIndex - lowerIdx; short downsampledSample = (short) ((1 - fraction) * sampleBuffer.get(lowerIdx) + fraction * sampleBuffer.get(upperIdx)); // Remove processed samples from buffer sampleBuffer.subList(0, (int) Math.ceil(resampleRatio)).clear(); // Pass this sample directly to your convolution function runConvolution(downsampledSample); } } } @Override public void onMarkerReached(AudioRecord recorder) {} }); audioRecord.setPositionNotificationPeriod(minBufferSize / 2); audioRecord.startRecording(); } // Replace this with your actual convolution logic private void runConvolution(short sample) { // Your convolution code here }
- Don't skip low-pass filtering: Without it, high-frequency content from the 44100Hz signal will alias into the 512Hz bandwidth, causing distortion. For better quality, use a more sophisticated FIR/IIR filter (you can generate filter kernels using window functions like Hann).
- Interpolation matters: Linear interpolation is a good balance between quality and performance; for higher fidelity, you can use cubic spline interpolation (but it's more compute-heavy).
- Convolution efficiency: If doing real-time convolution, consider using FFT-based convolution instead of direct convolution to reduce computation time.
内容的提问来源于stack exchange,提问作者Igor Vurdelja

