diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/audio/TeeAudioProcessor.java | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/audio/TeeAudioProcessor.java')
-rw-r--r-- | mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/audio/TeeAudioProcessor.java | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/audio/TeeAudioProcessor.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/audio/TeeAudioProcessor.java new file mode 100644 index 0000000000..42f151c5be --- /dev/null +++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/audio/TeeAudioProcessor.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mozilla.thirdparty.com.google.android.exoplayer2.audio; + +import androidx.annotation.Nullable; +import org.mozilla.thirdparty.com.google.android.exoplayer2.C; +import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions; +import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Log; +import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Audio processor that outputs its input unmodified and also outputs its input to a given sink. + * This is intended to be used for diagnostics and debugging. + * + * <p>This audio processor can be inserted into the audio processor chain to access audio data + * before/after particular processing steps have been applied. For example, to get audio output + * after playback speed adjustment and silence skipping have been applied it is necessary to pass a + * custom {@link org.mozilla.thirdparty.com.google.android.exoplayer2audio.DefaultAudioSink.AudioProcessorChain} when + * creating the audio sink, and include this audio processor after all other audio processors. + */ +public final class TeeAudioProcessor extends BaseAudioProcessor { + + /** A sink for audio buffers handled by the audio processor. */ + public interface AudioBufferSink { + + /** Called when the audio processor is flushed with a format of subsequent input. */ + void flush(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding); + + /** + * Called when data is written to the audio processor. + * + * @param buffer A read-only buffer containing input which the audio processor will handle. + */ + void handleBuffer(ByteBuffer buffer); + } + + private final AudioBufferSink audioBufferSink; + + /** + * Creates a new tee audio processor, sending incoming data to the given {@link AudioBufferSink}. + * + * @param audioBufferSink The audio buffer sink that will receive input queued to this audio + * processor. + */ + public TeeAudioProcessor(AudioBufferSink audioBufferSink) { + this.audioBufferSink = Assertions.checkNotNull(audioBufferSink); + } + + @Override + public AudioFormat onConfigure(AudioFormat inputAudioFormat) { + // This processor is always active (if passed to the sink) and outputs its input. + return inputAudioFormat; + } + + @Override + public void queueInput(ByteBuffer inputBuffer) { + int remaining = inputBuffer.remaining(); + if (remaining == 0) { + return; + } + audioBufferSink.handleBuffer(inputBuffer.asReadOnlyBuffer()); + replaceOutputBuffer(remaining).put(inputBuffer).flip(); + } + + @Override + protected void onQueueEndOfStream() { + flushSinkIfActive(); + } + + @Override + protected void onReset() { + flushSinkIfActive(); + } + + private void flushSinkIfActive() { + if (isActive()) { + audioBufferSink.flush( + inputAudioFormat.sampleRate, inputAudioFormat.channelCount, inputAudioFormat.encoding); + } + } + + /** + * A sink for audio buffers that writes output audio as .wav files with a given path prefix. When + * new audio data is handled after flushing the audio processor, a counter is incremented and its + * value is appended to the output file name. + * + * <p>Note: if writing to external storage it's necessary to grant the {@code + * WRITE_EXTERNAL_STORAGE} permission. + */ + public static final class WavFileAudioBufferSink implements AudioBufferSink { + + private static final String TAG = "WaveFileAudioBufferSink"; + + private static final int FILE_SIZE_MINUS_8_OFFSET = 4; + private static final int FILE_SIZE_MINUS_44_OFFSET = 40; + private static final int HEADER_LENGTH = 44; + + private final String outputFileNamePrefix; + private final byte[] scratchBuffer; + private final ByteBuffer scratchByteBuffer; + + private int sampleRateHz; + private int channelCount; + @C.PcmEncoding private int encoding; + @Nullable private RandomAccessFile randomAccessFile; + private int counter; + private int bytesWritten; + + /** + * Creates a new audio buffer sink that writes to .wav files with the given prefix. + * + * @param outputFileNamePrefix The prefix for output files. + */ + public WavFileAudioBufferSink(String outputFileNamePrefix) { + this.outputFileNamePrefix = outputFileNamePrefix; + scratchBuffer = new byte[1024]; + scratchByteBuffer = ByteBuffer.wrap(scratchBuffer).order(ByteOrder.LITTLE_ENDIAN); + } + + @Override + public void flush(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) { + try { + reset(); + } catch (IOException e) { + Log.e(TAG, "Error resetting", e); + } + this.sampleRateHz = sampleRateHz; + this.channelCount = channelCount; + this.encoding = encoding; + } + + @Override + public void handleBuffer(ByteBuffer buffer) { + try { + maybePrepareFile(); + writeBuffer(buffer); + } catch (IOException e) { + Log.e(TAG, "Error writing data", e); + } + } + + private void maybePrepareFile() throws IOException { + if (randomAccessFile != null) { + return; + } + RandomAccessFile randomAccessFile = new RandomAccessFile(getNextOutputFileName(), "rw"); + writeFileHeader(randomAccessFile); + this.randomAccessFile = randomAccessFile; + bytesWritten = HEADER_LENGTH; + } + + private void writeFileHeader(RandomAccessFile randomAccessFile) throws IOException { + // Write the start of the header as big endian data. + randomAccessFile.writeInt(WavUtil.RIFF_FOURCC); + randomAccessFile.writeInt(-1); + randomAccessFile.writeInt(WavUtil.WAVE_FOURCC); + randomAccessFile.writeInt(WavUtil.FMT_FOURCC); + + // Write the rest of the header as little endian data. + scratchByteBuffer.clear(); + scratchByteBuffer.putInt(16); + scratchByteBuffer.putShort((short) WavUtil.getTypeForPcmEncoding(encoding)); + scratchByteBuffer.putShort((short) channelCount); + scratchByteBuffer.putInt(sampleRateHz); + int bytesPerSample = Util.getPcmFrameSize(encoding, channelCount); + scratchByteBuffer.putInt(bytesPerSample * sampleRateHz); + scratchByteBuffer.putShort((short) bytesPerSample); + scratchByteBuffer.putShort((short) (8 * bytesPerSample / channelCount)); + randomAccessFile.write(scratchBuffer, 0, scratchByteBuffer.position()); + + // Write the start of the data chunk as big endian data. + randomAccessFile.writeInt(WavUtil.DATA_FOURCC); + randomAccessFile.writeInt(-1); + } + + private void writeBuffer(ByteBuffer buffer) throws IOException { + RandomAccessFile randomAccessFile = Assertions.checkNotNull(this.randomAccessFile); + while (buffer.hasRemaining()) { + int bytesToWrite = Math.min(buffer.remaining(), scratchBuffer.length); + buffer.get(scratchBuffer, 0, bytesToWrite); + randomAccessFile.write(scratchBuffer, 0, bytesToWrite); + bytesWritten += bytesToWrite; + } + } + + private void reset() throws IOException { + RandomAccessFile randomAccessFile = this.randomAccessFile; + if (randomAccessFile == null) { + return; + } + + try { + scratchByteBuffer.clear(); + scratchByteBuffer.putInt(bytesWritten - 8); + randomAccessFile.seek(FILE_SIZE_MINUS_8_OFFSET); + randomAccessFile.write(scratchBuffer, 0, 4); + + scratchByteBuffer.clear(); + scratchByteBuffer.putInt(bytesWritten - 44); + randomAccessFile.seek(FILE_SIZE_MINUS_44_OFFSET); + randomAccessFile.write(scratchBuffer, 0, 4); + } catch (IOException e) { + // The file may still be playable, so just log a warning. + Log.w(TAG, "Error updating file size", e); + } + + try { + randomAccessFile.close(); + } finally { + this.randomAccessFile = null; + } + } + + private String getNextOutputFileName() { + return Util.formatInvariant("%s-%04d.wav", outputFileNamePrefix, counter++); + } + } +} |