Android平台下如何在现有音频文件上叠加录制音频(不改变时长)
Hey there! It sounds like you're mixing up audio concatenation (sticking files end-to-end) with audio mixing/overlay (blending two audio streams together in time)—totally an easy mix-up when you're starting out. Let's walk through exactly how to pull off the overlay you want, keeping the original file's duration intact.
Core Concept First
Audio overlay works by combining the raw PCM (Pulse-Code Modulation) sample data of both your original audio and the recorded audio. For each moment in time, you add the sample values from both sources (with volume scaling to avoid clipping), then encode the combined samples back into a final audio file.
Step 1: Align Audio Parameters
First, make sure both your original audio and the recorded audio match on these critical parameters—if they don't, you'll get distortion or weird sync issues:
- Sample rate (e.g., 44100 Hz, 48000 Hz)
- Bit depth (e.g., 16-bit, which is standard for Android)
- Channel count (mono vs stereo)
When setting up your AudioRecord for recording, use the same parameters as your original file. You can extract these parameters from the original file using MediaExtractor and MediaFormat.
Step 2: Extract PCM from the Original Audio
You'll need to decode the original audio file into raw PCM data. Here's a simplified way to do that:
private byte[] extractPCMFromFile(String filePath) throws IOException { MediaExtractor extractor = new MediaExtractor(); extractor.setDataSource(filePath); int audioTrackIndex = -1; for (int i = 0; i < extractor.getTrackCount(); i++) { MediaFormat format = extractor.getTrackFormat(i); String mime = format.getString(MediaFormat.KEY_MIME); if (mime.startsWith("audio/")) { audioTrackIndex = i; break; } } if (audioTrackIndex == -1) throw new IOException("No audio track found"); extractor.selectTrack(audioTrackIndex); MediaFormat format = extractor.getTrackFormat(audioTrackIndex); MediaCodec codec = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME)); codec.configure(format, null, null, 0); codec.start(); ByteBuffer[] inputBuffers = codec.getInputBuffers(); ByteBuffer[] outputBuffers = codec.getOutputBuffers(); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); ByteArrayOutputStream pcmOutputStream = new ByteArrayOutputStream(); boolean isDone = false; while (!isDone) { int inputBufferIndex = codec.dequeueInputBuffer(10000); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; int sampleSize = extractor.readSampleData(inputBuffer, 0); if (sampleSize < 0) { codec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); isDone = true; } else { codec.queueInputBuffer(inputBufferIndex, 0, sampleSize, extractor.getSampleTime(), 0); extractor.advance(); } } int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 10000); if (outputBufferIndex >= 0) { ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; byte[] chunk = new byte[bufferInfo.size]; outputBuffer.get(chunk); outputBuffer.clear(); pcmOutputStream.write(chunk); codec.releaseOutputBuffer(outputBufferIndex, false); if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { isDone = true; } } } codec.stop(); codec.release(); extractor.release(); return pcmOutputStream.toByteArray(); }
Step 3: Record Audio to PCM
Use AudioRecord to capture your live audio into raw PCM. Make sure the parameters match the original file:
private byte[] recordAudio(int sampleRate, int channelConfig, int audioFormat, int durationMs) throws InterruptedException { int bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat); AudioRecord recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, channelConfig, audioFormat, bufferSize); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[bufferSize]; recorder.startRecording(); long startTime = System.currentTimeMillis(); while (System.currentTimeMillis() - startTime < durationMs) { int read = recorder.read(buffer, 0, bufferSize); if (read > 0) { outputStream.write(buffer, 0, read); } } recorder.stop(); recorder.release(); return outputStream.toByteArray(); }
Note: Use the original audio's duration for durationMs to ensure the recorded audio matches the length (truncate or pad with zeros if needed).
Step 4: Mix the PCM Data
This is the key part—blending the two PCM streams. For 16-bit audio (the most common), each sample is 2 bytes. We'll scale each sample to avoid clipping (since adding two full-volume samples would exceed the 16-bit range):
private byte[] mixPCM(byte[] originalPCM, byte[] recordedPCM) { // Ensure both arrays are the same length (pad shorter with zeros if needed) int minLength = Math.min(originalPCM.length, recordedPCM.length); int maxLength = Math.max(originalPCM.length, recordedPCM.length); byte[] mixedPCM = new byte[maxLength]; for (int i = 0; i < minLength; i += 2) { // Convert 2 bytes to 16-bit integer for each sample short originalSample = (short) ((originalPCM[i] & 0xFF) | (originalPCM[i+1] << 8)); short recordedSample = (short) ((recordedPCM[i] & 0xFF) | (recordedPCM[i+1] << 8)); // Mix with scaling to prevent clipping (adjust 0.5 to change volume balance) short mixedSample = (short) ((originalSample * 0.5) + (recordedSample * 0.5)); // Convert back to 2 bytes mixedPCM[i] = (byte) (mixedSample & 0xFF); mixedPCM[i+1] = (byte) ((mixedSample >> 8) & 0xFF); } // Pad the remaining bytes with zeros if one stream is longer if (maxLength > minLength) { byte[] source = originalPCM.length > recordedPCM.length ? originalPCM : recordedPCM; System.arraycopy(source, minLength, mixedPCM, minLength, maxLength - minLength); } return mixedPCM; }
Adjust the 0.5 factors to control the volume balance between the original and recorded audio (e.g., 0.6 for original, 0.4 for recorded if you want the original louder).
Step 5: Encode Mixed PCM Back to Audio File
Finally, encode the mixed PCM data back into a standard audio format like AAC or MP3. You can use MediaCodec for encoding here, similar to how we decoded the original file.
Key Notes to Avoid Headaches
- Clipping: Always scale your samples when mixing—if you don't, loud parts of both audio streams will cause distortion when their sum exceeds the maximum 16-bit value.
- Sync: Make sure the start time of the recorded audio aligns with the start of the original file (the code above assumes they start at the same time).
- Performance: For large audio files, process PCM in chunks instead of loading the entire file into memory to avoid OutOfMemoryErrors.
内容的提问来源于stack exchange,提问作者user9119623

