Skip to content

Instantly share code, notes, and snippets.

@nift4

nift4/a.patch Secret

Created February 28, 2025 13:31
Show Gist options
  • Select an option

  • Save nift4/5e5f6f515b5eef0522cfe06f7e01b6bf to your computer and use it in GitHub Desktop.

Select an option

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)
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