Onion Courier
2024-12-27 21:45:42 UTC
Reply
Permalink(download and decode with Python3 code below)
import argparse
import math
import numpy as np
import struct
import sys
from scipy.fftpack import fft
SAMPLE_RATE = 44100
DURATION = 0.2
FFT_SIZE = 8820
AMPLITUDE_8BIT = 127
AMPLITUDE_16BIT = 32760
BASE_FREQ = 440.0
FREQ_STEP = 25.0 # Larger step size since we only need 16 values
def generate_tone_buffer(freq, use_16bit):
num_samples = int(SAMPLE_RATE * DURATION)
amplitude = AMPLITUDE_16BIT if use_16bit else AMPLITUDE_8BIT
bytes_per_sample = 2 if use_16bit else 1
buf = bytearray(num_samples * bytes_per_sample)
for i in range(num_samples):
t = i / SAMPLE_RATE
window = 0.5 * (1 - math.cos(2 * math.pi * i / (num_samples - 1)))
sample = int(amplitude * math.sin(2 * math.pi * freq * t) * window)
if use_16bit:
struct.pack_into('>h', buf, i * 2, sample)
else:
buf[i] = sample + AMPLITUDE_8BIT
return buf
def write_au_header(use_16bit):
encoding = 0x03 if use_16bit else 0x02
header = bytearray([
0x2e, 0x73, 0x6e, 0x64,
0x00, 0x00, 0x00, 0x20,
0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, encoding,
0x00, 0x00, 0xac, 0x44,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
])
sys.stdout.buffer.write(header)
def encode_tones(data, use_16bit, tone_buffers):
write_au_header(use_16bit)
hex_str = data.hex()
for c in hex_str:
index = int(c, 16)
sys.stdout.buffer.write(tone_buffers[index])
def detect_frequency(samples):
windowed_samples = samples * np.hanning(len(samples))
spectrum = fft(windowed_samples)
magnitude = np.abs(spectrum[:len(spectrum) // 2])
peak_index = np.argmax(magnitude)
freq = peak_index * SAMPLE_RATE / FFT_SIZE
if peak_index > 0 and peak_index < FFT_SIZE // 2 - 1:
alpha = magnitude[peak_index - 1]
beta = magnitude[peak_index]
gamma = magnitude[peak_index + 1]
correction = 0.5 * (alpha - gamma) / (alpha - 2 * beta + gamma)
freq += correction * SAMPLE_RATE / FFT_SIZE
return freq
def freq_to_hex(freq):
index = round((freq - BASE_FREQ) / FREQ_STEP)
if 0 <= index < 16:
return format(index, 'x')
return None
def decode_tones(input_data, use_16bit):
bytes_per_sample = 2 if use_16bit else 1
input_data = input_data[24:]
num_samples = len(input_data) // bytes_per_sample
if use_16bit:
samples = np.frombuffer(input_data, dtype='>i2').astype(np.float64) / AMPLITUDE_16BIT
else:
samples = (np.frombuffer(input_data, dtype=np.uint8).astype(np.float64) - AMPLITUDE_8BIT) / AMPLITUDE_8BIT
hex_output = []
for i in range(0, num_samples, FFT_SIZE):
if i + FFT_SIZE > num_samples:
break
window = samples[i:i + FFT_SIZE]
freq = detect_frequency(window)
digit = freq_to_hex(freq)
if digit is not None:
hex_output.append(digit)
decoded_data = bytes.fromhex(''.join(hex_output))
sys.stdout.buffer.write(decoded_data)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-d', '--decode', action='store_true', help='Decode mode')
parser.add_argument('-16', '--use16bit', action='store_true', help='Use 16-bit audio (default: 8-bit)')
args = parser.parse_args()
tone_buffers = [
generate_tone_buffer(BASE_FREQ + i * FREQ_STEP, args.use16bit)
for i in range(16)
]
input_data = sys.stdin.buffer.read()
if args.decode:
decode_tones(input_data, args.use16bit)
else:
encode_tones(input_data, args.use16bit, tone_buffers)
if __name__ == '__main__':
main()