-
-
Save nift4/5e5f6f515b5eef0522cfe06f7e01b6bf to your computer and use it in GitHub Desktop.
applies to https://github.com/AkaneTan/Gramophone/commit/42164793cc3f12384ae1c39ca32734ced6de1215 ; CAUTION: very dumb, wrong code and a lot of noise! volume warning! (but it's enough for a PoC)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/app/src/main/java/Resampler2.java b/app/src/main/java/Resampler2.java | |
| new file mode 100644 | |
| index 00000000..0730e7d7 | |
| --- /dev/null | |
| +++ b/app/src/main/java/Resampler2.java | |
| @@ -0,0 +1,160 @@ | |
| +//https://gist.github.com/mattmalec/6ceee1f3961ff3068727ca98ff199fab | |
| +public class Resampler2 { | |
| + | |
| + public static byte[] resample(byte[] data, int length, boolean stereo, int inFrequency, int outFrequency) { | |
| + if (inFrequency < outFrequency) | |
| + return upsample(data, length, stereo, inFrequency, outFrequency); | |
| + | |
| + if (inFrequency > outFrequency) | |
| + return downsample(data, length, stereo, inFrequency, outFrequency); | |
| + | |
| + return trimArray(data, length); | |
| + } | |
| + | |
| + /** | |
| + * Basic upsampling algorithm. Uses linear approximation to fill in the | |
| + * missing data. | |
| + * | |
| + * @param data Input data | |
| + * @param length The current size of the input array (usually, data.length) | |
| + * @param inputIsStereo True if input is inputIsStereo | |
| + * @param inFrequency Frequency of input | |
| + * @param outFrequency Frequency of output | |
| + * | |
| + * @return Upsampled audio data | |
| + */ | |
| + private static byte[] upsample(byte[] data, int length, boolean inputIsStereo, int inFrequency, int outFrequency) { | |
| + | |
| + // Special case for no action | |
| + if (inFrequency == outFrequency) | |
| + return trimArray(data, length); | |
| + | |
| + double scale = (double) inFrequency / (double) outFrequency; | |
| + double pos = 0; | |
| + byte[] output; | |
| + | |
| + if (!inputIsStereo) { | |
| + output = new byte[(int) (length / scale)]; | |
| + for (int i = 0; i < output.length; i++) { | |
| + int inPos = (int) pos; | |
| + double proportion = pos - inPos; | |
| + if (inPos >= length - 1) { | |
| + inPos = length - 2; | |
| + proportion = 1; | |
| + } | |
| + | |
| + output[i] = (byte) Math.round(data[inPos] * (1 - proportion) + data[inPos + 1] * proportion); | |
| + pos += scale; | |
| + } | |
| + } else { | |
| + output = new byte[2 * (int) ((length / 2) / scale)]; | |
| + for (int i = 0; i < output.length / 2; i++) { | |
| + int inPos = (int) pos; | |
| + double proportion = pos - inPos; | |
| + | |
| + int inRealPos = inPos * 2; | |
| + if (inRealPos >= length - 3) { | |
| + inRealPos = length - 4; | |
| + proportion = 1; | |
| + } | |
| + | |
| + output[i * 2] = (byte) Math.round(data[inRealPos] * (1 - proportion) + data[inRealPos + 2] * proportion); | |
| + output[i * 2 + 1] = (byte) Math.round(data[inRealPos + 1] * (1 - proportion) + data[inRealPos + 3] * proportion); | |
| + pos += scale; | |
| + } | |
| + } | |
| + | |
| + return output; | |
| + } | |
| + | |
| + /** | |
| + * Basic downsampling algorithm. Uses linear approximation to reduce data. | |
| + * | |
| + * @param data Input data | |
| + * @param length The current size of the input array (usually, data.length) | |
| + * @param inputIsStereo True if input is inputIsStereo | |
| + * @param inFrequency Frequency of input | |
| + * @param outFrequency Frequency of output | |
| + * | |
| + * @return Downsampled audio data | |
| + */ | |
| + private static byte[] downsample(byte[] data, int length, boolean inputIsStereo, int inFrequency, int outFrequency) { | |
| + | |
| + // Special case for no action | |
| + if (inFrequency == outFrequency) | |
| + return trimArray(data, length); | |
| + | |
| + double scale = (double) outFrequency / (double) inFrequency; | |
| + byte[] output; | |
| + double pos = 0; | |
| + int outPos = 0; | |
| + | |
| + if (!inputIsStereo) { | |
| + double sum = 0; | |
| + output = new byte[(int) (length * scale)]; | |
| + int inPos = 0; | |
| + | |
| + while (outPos < output.length) { | |
| + double firstVal = data[inPos++]; | |
| + double nextPos = pos + scale; | |
| + if (nextPos >= 1) { | |
| + sum += firstVal * (1 - pos); | |
| + output[outPos++] = (byte) Math.round(sum); | |
| + nextPos -= 1; | |
| + sum = nextPos * firstVal; | |
| + } else { | |
| + sum += scale * firstVal; | |
| + } | |
| + pos = nextPos; | |
| + | |
| + if (inPos >= length && outPos < output.length) { | |
| + output[outPos++] = (byte) Math.round(sum / pos); | |
| + } | |
| + } | |
| + } else { | |
| + double sum1 = 0, sum2 = 0; | |
| + output = new byte[2 * (int) ((length / 2) * scale)]; | |
| + int inPos = 0; | |
| + | |
| + while (outPos < output.length) { | |
| + double firstVal = data[inPos++], nextVal = data[inPos++]; | |
| + double nextPos = pos + scale; | |
| + if (nextPos >= 1) { | |
| + sum1 += firstVal * (1 - pos); | |
| + sum2 += nextVal * (1 - pos); | |
| + output[outPos++] = (byte) Math.round(sum1); | |
| + output[outPos++] = (byte) Math.round(sum2); | |
| + nextPos -= 1; | |
| + sum1 = nextPos * firstVal; | |
| + sum2 = nextPos * nextVal; | |
| + } else { | |
| + sum1 += scale * firstVal; | |
| + sum2 += scale * nextVal; | |
| + } | |
| + pos = nextPos; | |
| + | |
| + if (inPos >= length && outPos < output.length) { | |
| + output[outPos++] = (byte) Math.round(sum1 / pos); | |
| + output[outPos++] = (byte) Math.round(sum2 / pos); | |
| + } | |
| + } | |
| + } | |
| + | |
| + return output; | |
| + } | |
| + | |
| + /** | |
| + * @param data Data | |
| + * @param length Length of valid data | |
| + * | |
| + * @return Array trimmed to length (or same array if it already is) | |
| + */ | |
| + public static byte[] trimArray(byte[] data, int length) { | |
| + if (data.length == length) | |
| + return data; | |
| + | |
| + byte[] output = new byte[length]; | |
| + System.arraycopy(output, 0, data, 0, length); | |
| + return output; | |
| + } | |
| +} | |
| diff --git a/app/src/main/kotlin/org/akanework/gramophone/logic/utils/exoplayer/GramophoneRenderFactory.kt b/app/src/main/kotlin/org/akanework/gramophone/logic/utils/exoplayer/GramophoneRenderFactory.kt | |
| index 35fcc2eb..5ad78b01 100644 | |
| --- a/app/src/main/kotlin/org/akanework/gramophone/logic/utils/exoplayer/GramophoneRenderFactory.kt | |
| +++ b/app/src/main/kotlin/org/akanework/gramophone/logic/utils/exoplayer/GramophoneRenderFactory.kt | |
| @@ -1,5 +1,6 @@ | |
| package org.akanework.gramophone.logic.utils.exoplayer | |
| +import Resampler2 | |
| import android.content.Context | |
| import android.os.Handler | |
| import android.os.Looper | |
| @@ -14,6 +15,8 @@ import androidx.media3.exoplayer.audio.ForwardingAudioSink | |
| import androidx.media3.exoplayer.mediacodec.MediaCodecSelector | |
| import androidx.media3.exoplayer.text.TextOutput | |
| import androidx.media3.exoplayer.video.VideoRendererEventListener | |
| +import java.nio.ByteBuffer | |
| +import java.nio.ByteOrder | |
| @OptIn(UnstableApi::class) | |
| class GramophoneRenderFactory(context: Context, | |
| @@ -75,7 +78,34 @@ class GramophoneRenderFactory(context: Context, | |
| outputChannels: IntArray? | |
| ) { | |
| configurationListener(inputFormat, specifiedBufferSize, outputChannels) | |
| - super.configure(inputFormat, specifiedBufferSize, outputChannels) | |
| + //super.configure(inputFormat, specifiedBufferSize, outputChannels)} | |
| + insr = inputFormat.sampleRate | |
| + super.configure(inputFormat.buildUpon().setSampleRate(192000).build(), specifiedBufferSize, outputChannels) | |
| + } | |
| + | |
| + private var insr = 0 | |
| + private var outBuffer: ByteBuffer? = null | |
| + | |
| + override fun handleBuffer( | |
| + buffer: ByteBuffer, | |
| + presentationTimeUs: Long, | |
| + encodedAccessUnitCount: Int | |
| + ): Boolean { | |
| + val sp = buffer.position() | |
| + val arr = ByteArray(buffer.remaining()) | |
| + buffer.get(arr) | |
| + val rs = Resampler2.resample(arr, arr.size, false, insr, 192000) | |
| + if (outBuffer == null) | |
| + outBuffer = ByteBuffer.allocate(99999999).order(ByteOrder.LITTLE_ENDIAN) | |
| + outBuffer!!.clear() | |
| + outBuffer!!.put(rs) | |
| + outBuffer!!.limit(outBuffer!!.position()) | |
| + outBuffer!!.rewind() | |
| + val ret = super.handleBuffer(outBuffer!!, presentationTimeUs, encodedAccessUnitCount) | |
| + buffer.position(sp + ((outBuffer!!.position()) * (insr / 192000))) | |
| + return ret | |
| } | |
| } | |
| + | |
| + | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment